19.2.0 • Published 6 months ago

@mmstack/resource v19.2.0

Weekly downloads
-
License
MIT
Repository
github
Last release
6 months ago

@mmstack/resource

npm version License

@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 mutationResource for handling data modifications, with callbacks for onMutate, onError, onSuccess, and onSettled.
  • 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

  1. Install mmstack-resource
npm install @mmstack/primitives
  1. 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()])),
  ],
};
  1. 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

19.2.0

6 months ago

19.1.4

6 months ago

19.1.3

6 months ago

19.1.2

6 months ago

19.1.1

6 months ago

19.1.0

8 months ago

19.0.10

8 months ago

19.0.9

8 months ago

19.0.8

8 months ago

19.0.7

8 months ago

19.0.6

8 months ago

19.0.5

8 months ago

19.0.4

8 months ago

19.0.3

8 months ago

19.0.2

8 months ago

19.0.1

8 months ago

19.0.0

8 months ago