withCallState()
import { withCallState } from '@angular-architects/ngrx-toolkit';
withCallState adds call state management capabilities to NgRx signal stores, tracking the status of asynchronous operations with built-in states for loading, loaded, and error conditions.
Basic Usage
The simplest way to use withCallState is without any configuration:
export const TodosStore = signalStore(
withCallState(),
// ... other features
);
This provides you with:
- A
callStatestate property of type'init' | 'loading' | 'loaded' | { error: string } - Computed signals:
loading,loaded, anderror - Helper methods:
setLoading(),setLoaded(), andsetError()
Use Cases
- Track loading states for async operations
- Handle error states consistently
- Manage multiple named collections of call states
Type Constraints
The call state can be one of these types:
'init'- Initial state'loading'- Operation in progress'loaded'- Operation completed successfully{ error: string }- Operation failed with error
Usage
import { withCallState, setLoading, setLoaded, setError } from '@angular-architects/ngrx-toolkit';
const store = signalStore(
withCallState(),
withMethods((store) => ({
async loadData() {
patchState(store, setLoading());
try {
// ... async operation
patchState(store, setLoaded());
} catch (error) {
patchState(store, setError(error));
}
},
})),
);
Named Collection
You can track state for a specific collection by providing a collection name:
export const TodosStore = signalStore(
withCallState({ collection: 'todos' }),
// ... other features
);
This provides:
- A
todosCallStatestate property - Computed signals:
todosLoading,todosLoaded, andtodosError - Helper methods with optional collection parameter
const store = signalStore(
withCallState({ collection: 'todos' }),
withMethods((store) => ({
async loadTodos() {
patchState(store, setLoading('todos'));
try {
// ... load todos
patchState(store, setLoaded('todos'));
} catch (error) {
patchState(store, setError(error, 'todos'));
}
},
})),
);
Multiple Collections
For managing multiple async operations, use the collections configuration:
export const TodosStore = signalStore(
withCallState({ collections: ['todos', 'categories'] }),
// ... other features
);
This creates separate states and signals for each collection:
- States:
todosCallState,categoriesCallState - Signals:
todosLoading,todosLoaded,todosError,categoriesLoading, etc.
const store = signalStore(
withCallState({ collections: ['todos', 'users'] }),
withMethods((store) => ({
async loadAll() {
patchState(store, setLoading('todos'), setLoading('users'));
// ... load data for both collections
},
})),
);
Helper Methods
setLoading()
Sets the call state to 'loading':
patchState(store, setLoading()); // Basic usage
patchState(store, setLoading('todos')); // With collection
setLoaded()
Sets the call state to 'loaded':
patchState(store, setLoaded()); // Basic usage
patchState(store, setLoaded('todos')); // With collection
setError()
Sets the call state to error with a message:
patchState(store, setError(error)); // Basic usage
patchState(store, setError(error, 'todos')); // With collection
Accessing State
Access the computed signals in your templates or component code:
@Component({
template: `
@if (store.loading()) {
<div>Loading...</div>
}
@if (store.error()) {
<div>Error: {{ store.error() }}</div>
}
@if (store.loaded()) {
<div>Content loaded!</div>
}
`,
})
export class MyComponent {
store = inject(MyStore);
}
For collections:
@Component({
template: `
@if (store.todosLoading()) {
<div>Loading todos...</div>
}
@if (store.categoriesLoading()) {
<div>Loading categories...</div>
}
`
})