Signal based HTTP cache and query state manager for Angular
npm install @frontkit-ng/signal-http-cacheA lightweight, Signal-powered HTTP caching library for Angular.



---
- Time-to-live based caching
- Stale-while-revalidate
- Request deduplication
- Pure Signals, no RxJS required
- Native fetch or provide your own (HttpClient, SSR, etc.)
- Auto cleanup via Angular DestroyRef
- Mutations for POST/PUT/PATCH/DELETE
- Retry with exponential backoff
- Race condition prevention
- Cache automatic invalidation
---
``bash`
npm install @frontkit-ng/signal-http-cache
---
@angular/core >=16.0.0
---
Use createQuery to fetch and cache data.
`ts
import { createQuery } from "@frontkit-ng/signal-http-cache";
const usersQuery = createQuery
// fetch data
await usersQuery.fetch();
// reactive state
usersQuery.data();
usersQuery.loading();
usersQuery.error();
`
`ts`
const query = createQuery("/api/data", {
ttl: 60000, // cache for 60 seconds
});
`ts`
const query = createQuery("/api/data", {
staleWhileRevalidate: true, // show stale data while fetching
});
Ignore cache, always fetch fresh data
`ts`
await query.fetch(true);
Mark cache as stale and abort any pending request
`ts`
query.invalidate();
---
Query keys determine how requests are cached.
`ts`
const query = createQuery(["/api/users", page(), searchTerm(), sortBy()]);
Each unique combination creates a separate cache entry, perfect for:
- Pagination
- Search/filtering
- Sorting
- Any dynamic parameters
---
Use createMutation for data modifications (POST, PUT, PATCH, DELETE).
`ts
import { createMutation } from "@frontkit-ng/signal-http-cache";
const updateUser = createMutation
method: "PUT",
onSuccess: () => toast.success("Saved!"),
onError: (err) => toast.error(err.message),
onFinally: () => closeModal(),
});
`
---
For DELETE/PUT/PATCH where the ID is in the URL:
`ts/api/todos/${id}
const deleteTodo = createMutation, {
method: "DELETE",
});
deleteTodo.mutate(5); // DELETE /api/todos/5
`
---
`ts`
const submitForm = createMutation
retry: 3,
retryDelay: (attempt) => 1000 2 * attempt, // exponential backoff
});
`ts
import { Component, OnInit } from "@angular/core";
import { createQuery, createMutation } from "@frontkit-ng/signal-http-cache";
interface Todo {
id: string;
title: string;
}
@Component({
selector: "app-todos",
standalone: true,
template:
@if (loading()) {
Loading...
,
})
export class TodosComponent implements OnInit {
private todosQuery = createQuery
addMutation = createMutation
onSuccess: () => this.todosQuery.fetch(true),
});
deleteMutation = createMutation, {
method: "DELETE",
invalidateKeys: ["/api/todos"],
});
todos = this.todosQuery.data;
loading = this.todosQuery.loading;
ngOnInit() {
this.todosQuery.fetch();
}
add(input: HTMLInputElement) {
if (input.value) {
this.addMutation.mutate({ title: input.value });
input.value = "";
}
}
delete(id: string) {
this.deleteMutation.mutate(id);
}
}
`
---
By default, the library uses the native browser fetch API. To use Angular's HttpClient instead (for interceptors, auth tokens, etc.), create an adapter:
This is optional - the library does not require HttpClient.
`ts
// http-client-adapter.ts
import { inject } from "@angular/core";
import { HttpClient, HttpErrorResponse } from "@angular/common/http";
import { firstValueFrom, catchError, of } from "rxjs";
export function httpClientAdapter(url: string, init?: RequestInit) {
const http = inject(HttpClient);
const method = init?.method ?? "GET";
const headers = init?.headers as Record
let body: any = undefined;
if (init?.body) {
if (typeof init.body === "string") {
try {
body = JSON.parse(init.body);
} catch {
body = init.body;
}
} else {
body = init.body;
}
}
return firstValueFrom(
http
.request
body,
headers,
observe: "response",
responseType: "json",
})
.pipe(
catchError((err: HttpErrorResponse) => {
return of({
ok: false,
status: err.status,
statusText: err.statusText,
body: err.error,
} as any);
})
)
).then((response: any) => {
const responseBody = response.body;
const bodyText =
typeof responseBody === "string"
? responseBody
: JSON.stringify(responseBody ?? "");
return {
ok: response.ok ?? (response.status >= 200 && response.status < 300),
status: response.status,
statusText: response.statusText,
json: () => Promise.resolve(responseBody),
text: () => Promise.resolve(bodyText), // ← required for createMutation
} as Response;
});
}
`
Pass your adapter as the third argument to createQuery or createMutation:
`ts
import { createQuery } from "@frontkit-ng/signal-http-cache";
import { httpClientAdapter } from "./http-client-adapter";
// query
const users = createQuery
"/api/users",
{ ttl: 60000 },
httpClientAdapter
);
// mutation
const addUser = createMutation
"/api/users",
{ onSuccess: () => users.fetch(true) },
httpClientAdapter
);
``
---
This project is licensed under the MIT License - see the LICENSE file for details.