@mmstack/resource
v21.0.0
Published
[](https://www.npmjs.com/package/@mmstack/resource) [](https://github.com/mihajm/mmstack/blob/master/packages/resource/LICENSE)
Downloads
252
Maintainers
Readme
@mmstack/resource
@mmstack/resource is an Angular library that provides powerful, signal-based primitives for managing asynchronous data fetching and mutations. It builds upon Angular's httpResource and offers features like caching, retries, refresh intervals, circuit breakers, and request deduplication, all while maintaining a fine-grained reactive graph. It's inspired by libraries like TanStack Query, but aims for a more Angular-idiomatic and signal-centric approach.
Features
- Signal-Based: Fully integrates with Angular's signal system for efficient change detection and reactivity.
- Caching: Built-in caching with configurable TTL (Time To Live) and stale-while-revalidate behavior. Supports custom cache key generation and respects HTTP caching headers.
- Retries: Automatic retries on failure with configurable backoff strategies.
- Refresh Intervals: Automatically refetch data at specified intervals.
- Circuit Breaker: Protects your application from cascading failures by temporarily disabling requests to failing endpoints.
- Request Deduplication: Avoids making multiple identical requests concurrently.
- Mutations: Provides a dedicated
mutationResourcefor handling data modifications, with callbacks foronMutate,onError,onSuccess, andonSettled. - Prefetching: Allows you to prefetch data into the cache, improving perceived performance.
- Extensible: Designed to be modular and extensible. You can easily add your own custom features or integrate with other libraries.
- TypeScript Support: A strong focus on typesafety
Quick Start
- Install mmstack-resource
npm install @mmstack/resource- Initialize the QueryCache & interceptors (optional)
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { ApplicationConfig } from '@angular/core';
import { createCacheInterceptor, createDedupeRequestsInterceptor, provideQueryCache } from '@mmstack/resource';
export const appConfig: ApplicationConfig = {
providers: [
// ..other providers
provideQueryCache(),
// --- Example of a more advanced setup ---
// provideQueryCache({
// persist: true, // Enable IndexedDB persistence
// version: 1, // Version for the cache schema
// syncTabs: true // enable BroadcastChannel
// }),
provideHttpClient(withInterceptors([createCacheInterceptor(), createDedupeRequestsInterceptor()])),
],
};- Use it :)
import { Injectable, isDevMode, untracked } from '@angular/core';
import { createCircuitBreaker, mutationResource, queryResource } from '@mmstack/resource';
type Post = {
userId: number;
id: number;
title: string;
body: string;
};
@Injectable({
providedIn: 'root',
})
export class PostsService {
private readonly endpoint = 'https://jsonplaceholder.typicode.com/posts';
private readonly cb = createCircuitBreaker();
readonly posts = queryResource<Post[]>(
() => ({
url: this.endpoint,
}),
{
keepPrevious: true, // keep data between requests
refresh: 5 * 60 * 1000, // refresh every 5 minutes
circuitBreaker: this.cb, // use shared circuit breaker use true if not sharing
retry: 3, // retry 3 times on error using default backoff
onError: (err) => {
if (!isDevMode()) return;
console.error(err);
}, // log errors in dev mode
defaultValue: [],
},
);
private readonly createPostResource = mutationResource(
(post: Post) => ({
url: this.endpoint,
method: 'POST',
body: post,
}),
{
circuitBreaker: this.cb, // use shared circuit breaker use true if not sharing
onMutate: (post: Post) => {
const prev = untracked(this.posts.value);
this.posts.set([...prev, post]); // optimistically update
return prev;
},
onError: (err, prev) => {
if (isDevMode()) console.error(err);
this.posts.set(prev); // rollback on error
},
onSuccess: (next) => {
this.posts.update((posts) => posts.map((p) => (p.id === next.id ? next : p))); // replace with value from server
},
},
);
createPost(post: Post) {
this.createPostResource.mutate(post); // send the request
}
}In-depth
For an in-depth explanation of the primitives & how they work check out this article: Fun-grained Reactivity in Angular: Part 3 - Resources
