Angular signals integration for Spoosh API toolkit
npm install @spoosh/angularAngular signals integration for Spoosh - injectRead, injectWrite, and injectInfiniteRead.
Documentation · Requirements: TypeScript >= 5.0, Angular >= 16.0
``bash`
npm install @spoosh/core @spoosh/angular
`typescript
import { Spoosh } from "@spoosh/core";
import { create } from "@spoosh/angular";
import { cachePlugin } from "@spoosh/plugin-cache";
const spoosh = new Spoosh
cachePlugin({ staleTime: 5000 }),
]);
export const { injectRead, injectWrite, injectInfiniteRead } = create(spoosh);
`
Fetch data with automatic caching and triggering using Angular signals.
`typescript
@Component({
template:
@if (users.loading()) {
,
})
export class UserListComponent {
users = injectRead((api) => api("users").GET());
}// With options
@Component({ ... })
export class UserListComponent {
isReady = signal(false);
// Recommended: Pass signal directly (shorter syntax)
users = injectRead(
(api) => api("users").GET({ query: { page: 1 } }),
{
staleTime: 10000,
enabled: this.isReady,
}
);
// With path parameters
userId = signal(123);
user = injectRead(
(api) => api("users/:id").GET({ params: { id: this.userId() } }),
{ enabled: () => this.userId() !== null }
);
}
`$3
Trigger mutations with loading and error states.
`typescript
@Component({
template: ,
})
export class CreateUserComponent {
title = signal(""); createUser = injectWrite((api) => api("users").POST);
async handleSubmit() {
const result = await this.createUser.trigger({
body: { name: this.title() },
});
if (result.data) {
// Success
}
}
}
// With path parameters
updateUser = injectWrite((api) => api("users/:id").PUT);
async updateUserName(userId: number, name: string) {
await this.updateUser.trigger({
params: { id: userId },
body: { name },
});
}
`$3
Bidirectional paginated data fetching with infinite scroll support.
`typescript
@Component({
template: @for (post of posts.data(); track post.id) {
}
@if (posts.canFetchNext()) {
}
,
})
export class PostListComponent {
posts = injectInfiniteRead(
(api) => api("posts").GET({ query: { page: 1 } }),
{
// Required: Check if next page exists
canFetchNext: ({ response }) => response?.meta.hasMore ?? false,
// Required: Build request for next page
nextPageRequest: ({ response, request }) => ({
query: { ...request.query, page: (response?.meta.page ?? 0) + 1 },
}),
// Required: Merge all responses into items
merger: (allResponses) => allResponses.flatMap((r) => r.items),
// Optional: Check if previous page exists
canFetchPrev: ({ response }) => (response?.meta.page ?? 1) > 1,
// Optional: Build request for previous page
prevPageRequest: ({ response, request }) => ({
query: { ...request.query, page: (response?.meta.page ?? 2) - 1 },
}),
}
);
}
`
| Option | Type | Default | Description |
| ---------------- | --------------------------------------------- | ------- | ------------------------------------ |
| enabled | boolean \| Signal | true | Whether to fetch automatically |staleTime
| | number | - | Cache stale time (from plugin-cache) |retries
| | number | - | Retry attempts (from plugin-retry) |
| + plugin options | - | - | Options from installed plugins |
Returns:
| Property | Type | Description |
| ---------- | ----------------------------- | ----------------------------------------- |
| data | Signal | Response data |error
| | Signal | Error if request failed |loading
| | Signal | True during initial load |fetching
| | Signal | True during any fetch |meta
| | Signal | Plugin metadata (e.g., transformedData) |trigger
| | () => Promise | Manually trigger fetch |abort
| | () => void | Abort current request |
Returns:
| Property | Type | Description |
| --------- | ----------------------------- | ---------------------------------- |
| trigger | (options) => Promise | Execute the mutation |data
| | Signal | Response data |error
| | Signal | Error if request failed |loading
| | Signal | True while mutation is in progress |meta
| | Signal | Plugin metadata |input
| | TriggerOptions \| undefined | Last trigger input |abort
| | () => void | Abort current request |
| Option | Type | Required | Description |
| ----------------- | --------------------------------------------- | -------- | ------------------------------- |
| canFetchNext | (ctx) => boolean | Yes | Check if next page exists |nextPageRequest
| | (ctx) => Partial | Yes | Build request for next page |merger
| | (allResponses) => TItem[] | Yes | Merge all responses into items |canFetchPrev
| | (ctx) => boolean | No | Check if previous page exists |prevPageRequest
| | (ctx) => Partial | No | Build request for previous page |enabled
| | boolean \| Signal | No | Whether to fetch automatically |
Context object passed to callbacks:
`typescript`
type Context
response: TData | undefined; // Latest response
allResponses: TData[]; // All fetched responses
request: TRequest; // Current request options
};
Returns:
| Property | Type | Description |
| -------------- | ------------------------------ | ------------------------------- |
| data | Signal | Merged items from all responses |allResponses
| | Signal | Array of all raw responses |loading
| | Signal | True during initial load |fetching
| | Signal | True during any fetch |fetchingNext
| | Signal | True while fetching next page |fetchingPrev
| | Signal | True while fetching previous |canFetchNext
| | Signal | Whether next page exists |canFetchPrev
| | Signal | Whether previous page exists |meta
| | Signal | Plugin metadata |fetchNext
| | () => Promise | Fetch the next page |fetchPrev
| | () => Promise | Fetch the previous page |refetch
| | () => Promise | Refetch all pages |abort
| | () => void | Abort current request |error
| | Signal