19.2.0 • Published 9 months ago

@mmstack/resource v19.2.0

Weekly downloads
-
License
MIT
Repository
github
Last release
9 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

9 months ago

19.1.4

9 months ago

19.1.3

9 months ago

19.1.2

9 months ago

19.1.1

9 months ago

19.1.0

10 months ago

19.0.10

10 months ago

19.0.9

10 months ago

19.0.8

11 months ago

19.0.7

11 months ago

19.0.6

11 months ago

19.0.5

11 months ago

19.0.4

11 months ago

19.0.3

11 months ago

19.0.2

11 months ago

19.0.1

11 months ago

19.0.0

11 months ago