npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@ngneat/cashew

v4.1.0

Published

A simple and flexible library that caches HTTP requests in Angular applications

Downloads

91,069

Readme

Caching is nut a problem!

Build Status MIT coc-badge commitizen PRs styled with prettier All Contributors ngneat

Features

✅ HTTP Caching ✅ State Management Mode ✅ Local Storage Support ✅ Handles Simultaneous Requests ✅ Automatic & Manual Cache Busting ✅ Hackable

A flexible and straightforward library that caches HTTP requests in Angular

Installation

$ npm install @ngneat/cashew

Usage

Use the provideHttpCache provider along with withHttpCacheInterceptor in your application providers:

import { provideHttpCache, withHttpCacheInterceptor } from '@ngneat/cashew';

bootstrapApplication(AppComponent, {
  providers: [provideHttpClient(withInterceptors([withHttpCacheInterceptor()])), provideHttpCache()]
});

And you're done! Now, when using Angular HttpClient, you can pass the withCache function as context, and it'll cache the response:

import { withCache } from '@ngneat/cashew';

@Injectable()
export class UsersService {
  constructor(private http: HttpClient) {}

  getUsers() {
    return this.http.get('api/users', {
      context: withCache()
    });
  }
}

It's as simple as that.

State Management Mode

When working with state management like Akita or ngrx, there is no need to save the data both in the cache and in the store because the store is the single source of truth. In such a case, the only thing we want is an indication of whether the data is in the cache.

We can change the mode option to stateManagement:

import { withCache } from '@ngneat/cashew';

@Injectable()
export class UsersService {
  constructor(private http: HttpClient) {}

  getUsers() {
    return this.http.get('api/users', {
      context: withCache({
        mode: 'stateManagement'
      })
    });
  }
}

Now instead of saving the actual response in the cache, it'll save a boolean and will return by default an EMPTY observable when the boolean resolves to true. You can change the returned source by using the returnSource option.

Local Storage

By default, caching is done to app memory. To switch to using local storage instead simply add:

import { provideHttpCache, withHttpCacheInterceptor, provideHttpCacheLocalStorageStrategy } from '@ngneat/cashew';

bootstrapApplication(AppComponent, {
  providers: [
    provideHttpClient(withInterceptors([withHttpCacheInterceptor()])),
    provideHttpCache(),
    provideHttpCacheLocalStorageStrategy()
  ]
});

To your providers list. Note that ttl will also be calculated via local storage in this instance.

Versioning

When working with localstorage, it's recommended to add a version:

import { withCache } from '@ngneat/cashew';

@Injectable()
export class UsersService {
  constructor(private http: HttpClient) {}

  getUsers() {
    return this.http.get('api/users', {
      context: withCache({
        version: 'v1',
        key: 'users'
      })
    });
  }
}

When you have a breaking change, change the version, and it'll delete the current cache automatically.

Config Options

Using the library, you might need to change the default behavior of the caching mechanism. You could do that by passing a configuration to the provideHttpCache function:

bootstrapApplication(AppComponent, {
  providers: [provideHttpClient(withInterceptors([withHttpCacheInterceptor()])), provideHttpCache(config)]
});

Let's go over each of the configuration options:

strategy

Defines the caching behavior. The library supports two different strategies:

  • explicit (default) - only caches API requests that explicitly use the withCache function
  • implicit - caches API requests that are of type GET and the response type is JSON. You can change this behavior by overriding the HttpCacheGuard provider. (See the Hackable section)
bootstrapApplication(AppComponent, {
  providers: [
    provideHttpClient(withInterceptors([withHttpCacheInterceptor()])),
    provideHttpCache({ strategy: 'implicit' })
  ]
});

ttl

Define the cache TTL (time to live) in milliseconds: (defaults to one hour)

bootstrapApplication(AppComponent, {
  providers: [provideHttpClient(withInterceptors([withHttpCacheInterceptor()])), provideHttpCache({ ttl: number })]
});

responseSerializer

By default, the registry returns the original response object. It can be dangerous if, for some reason, you mutate it. To change this behavior, you can clone the response before getting it:

bootstrapApplication(AppComponent, {
  providers: [
    provideHttpClient(withInterceptors([withHttpCacheInterceptor()])),
    provideHttpCache({
      responseSerializer(body) {
        return cloneDeep(body);
      }
    })
  ]
});

API

WithCache

Currently, there is no way in Angular to pass metadata to an interceptor. The withCache function uses the params object to pass the config and removes it afterward in the interceptor. The function receives four optional params that are postfixed with a $ sign so it'll not conflicts with others:

  • cache - Whether to cache the request (defaults to true)
  • ttl - TTL that will override the global
  • key - Custom key. (defaults to the request URL including any query params)
  • bucket - The bucket in which we save the keys
  • version - To use when working with localStorage (see Versioning).
  • clearCachePredicate(previousRequest, currentRequest) - Return true to clear the cache for this key
  • context - Allow chaining function call that returns an HttpContext.
import { requestDataChanged, withCache } from '@ngneat/cashew';

@Injectable()
export class UsersService {
  constructor(private http: HttpClient) {}

  getUsers() {
    return this.http.get('api/users', {
      context: withCache({
        withCache: false,
        ttl: 40000,
        key: 'users',
        clearCachePredicate: requestDataChanged
      })
    });
  }
}

When you need to call another function that returns an HttpContext, you can provide the context option.

import { withCache } from '@ngneat/cashew';
import { withLoadingSpinner } from '@another/library'; // <-- function that returns an HttpContext

@Injectable()
export class TodosService {
  constructor(private http: HttpClient) {}

  getTodos() {
    return this.http.get('api/todos', {
      context: withCache({
        context: withLoadingSpinner()
      })
    });
  }
}

CacheManager

The CacheManager provider, exposes an API to update and query the cache registry:

  • get<T>(key: string): HttpResponse<T> - Get the HttpResponse from the cache
  • has(key: string) - Returns a boolean indicates whether the provided key exists in the cache
  • set(key: string, body: any, { ttl, bucket }) - Set manually a new entry in the cache
  • delete(key: string | CacheBucket) - Delete from the cache

CacheBucket

CacheBucket can be useful when we need to buffer multiple requests and invalidate them at some point. For example:

import { withCache, CacheBucket } from '@ngneat/cashew';

@Injectable()
export class TodosService {
  todosBucket = new CacheBucket();

  constructor(
    private http: HttpClient,
    private manager: HttpCacheManager
  ) {}

  getTodo(id) {
    return this.http.get(`todos/${id}`, {
      context: withCache({
        bucket: this.todosBucket
      })
    });
  }

  invalidateTodos() {
    this.manager.delete(this.todosBucket);
  }
}

Now when we call the invalidateTodos method, it'll automatically delete all the ids that it buffered. CacheBucket also exposes the add, has, delete, and clear methods.

Hack the Library

  • HttpCacheStorage - The storage to use: (defaults to in-memory storage)
abstract class HttpCacheStorage {
  abstract has(key: string): boolean;
  abstract get(key: string): HttpResponse<any>;
  abstract set(key: string, response: HttpResponse<any>): void;
  abstract delete(key?: string): void;
}
  • KeySerializer - Generate the cache key based on the request: (defaults to request.urlWithParams)
export abstract class KeySerializer {
  abstract serialize(request: HttpRequest): string;
}
  • HttpCacheGuard - When using the implicit strategy it first verifies that canActivate is truthy:
export abstract class HttpCacheGuard {
  abstract canActivate(request: HttpCacheHttpRequestRequest): boolean;
}

It defaults to request.method === 'GET' && request.responseType === 'json'.

  • TTLManager - A class responsible for managing the requests TTL:
abstract class TTLManager {
  abstract isValid(key: string): boolean;
  abstract set(key: string, ttl?: number): void;
  abstract delete(key?: string): void;
}

Compatability matrix

| Cashew | Angular | |--------|---------| | ^4.0.0 | ^17.0.0 | | 3.1.0 | >13.0.0 < 17 | | 3.0.0 | ^13.0.0 | | ^2.0.0 | ^12.0.0 | | ^1.0.0 | ^10.0.0 |

Contributors ✨

Thanks go to these wonderful people (emoji key):

This project follows the all-contributors specification. Contributions of any kind welcome!