query
The query primitive manages server data fetching - that can be easily extended for syncing with localStorage, reacting to mutations (that unlock optimistic update, update, reload on failed...).
Import
typescript
import { query } from '@ng-angular-stack/craft';Basic Examples
Params-based query
typescript
const myQuery = query({
params: { id: 1 },
loader: async ({ params }) => {
const response = await fetch(`/api/users/${params.id}`);
return response.json();
},
});
// Access query state
console.log(myQuery.value()); // User data
console.log(myQuery.isLoading()); // true/false
console.log(myQuery.error()); // Error or undefinedIdentifier-based queries (for parallel queries)
typescript
const userId = signal<number | undefined>(undefined);
const query = query({
params: userId,
identifier: (id) => id,
loader: async ({ params: userId }) => {
const response = await fetch(`/api/users/${userId}`);
return response.json();
},
});
// Both queries run in parallel
userId.set(1);
// later
userId.set(2);
// Once all queries are resolved
console.log(query.select('1').value()); // User 1 data
console.log(query.select('2').value()); // User 2 dataReact to mutation with insertReactOnMutation and persist in local storage
typescript
import { insertReactOnMutation } from '@ng-angular-stack/craft';
const updateUserMutation = mutation({
method: (data: { id: string; name: string; email: string }) => data,
loader: async ({ params }) => {
const response = await fetch(`/api/users/${params.id}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(params),
});
return response.json();
},
});
const userQuery = query(
{
params: () => ({ userId: currentUserId() }),
loader: async ({ params }) => {
const response = await fetch(`/api/users/${params.userId}`);
return response.json();
},
},
insertReactOnMutation(updateUserMutation, {
// Optimistically update while mutation is loading
optimisticPatch: {
name: ({ mutationParams }) => mutationParams.name,
email: ({ mutationParams }) => mutationParams.email,
},
// Reload the query if updateUserMutation failed
reload: { onMutationError: true },
}),
insertLocalStoragePersister({
storeName: 'demo-app',
key: 'user-query',
}),
);
// When mutation is triggered, query updates immediately (optimistic)
updateUserMutation.mutate({
id: '123',
name: 'New Name',
email: 'new@email.com',
});
// userQuery.value() is updated optimistically
// When mutation completes, patch confirms the changeImportant Notes
⚠️ Injection Context: This function must be called within an injection context. If called outside, it will only return an object containing the configuration under _config.
Query with insertions for custom methods
typescript
const todosQuery = query(
{
params: () => ({ completed: showCompleted() }),
loader: async ({ params }) => {
const response = await fetch(`/api/todos?completed=${params.completed}`);
return response.json();
},
},
({ value, isLoading }) => ({
count: computed(() => value()?.length ?? 0),
isEmpty: computed(() => !isLoading() && value()?.length === 0),
}),
);
// Access custom computed properties
console.log(todosQuery.count()); // Number of todos
console.log(todosQuery.isEmpty()); // true/falsePreserve previous value to avoid flickering
typescript
const postsQuery = query({
params: () => ({ page: currentPage() }),
preservePreviousValue: () => true, // Keep showing old data while loading
loader: async ({ params }) => {
const response = await fetch(`/api/posts?page=${params.page}`);
return response.json();
},
});
// When page changes, old data remains visible until new data loadsBest Practices
✅ Use preservePreviousValue to avoid flickering during navigation ✅ Use insertions to add custom computed properties and methods
See Also
- mutation - For server updates
- asyncMethod - For one-off async operations
- insertReactOnMutation - React to mutation changes
- Store Query - For store integration