@mmstack/resource v19.2.0
@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/primitives- 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(),
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(
() => ({
url: this.endpoint,
method: '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({ body: 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
6 months ago
6 months ago
6 months ago
6 months ago
6 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago
8 months ago