• Stars
    star
    677
  • Rank 66,694 (Top 2 %)
  • Language
    TypeScript
  • License
    MIT License
  • Created almost 5 years ago
  • Updated 10 months ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

🐿 A flexible and straightforward library that caches HTTP requests in Angular

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

Inject the HttpCacheInterceptorModule module along with HttpClientModule into you root module:

import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { HttpCacheInterceptorModule } from '@ngneat/cashew';

@NgModule({
  imports: [HttpClientModule, HttpCacheInterceptorModule.forRoot()],
  bootstrap: [AppComponent]
})
export class AppModule {}

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 { 
  HttpCacheInterceptorModule, 
  useHttpCacheLocalStorage 
} from '@ngneat/cashew';

@NgModule({
  imports: [HttpClientModule, HttpCacheInterceptorModule.forRoot()],
  providers: [useHttpCacheLocalStorage],
  bootstrap: [AppComponent]
})
export class AppModule {}

To your AppModule 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 (a partial HttpCacheConfig object) to the static forRoot method of the HttpCacheInterceptorModule module.

{ provide: HTTP_CACHE_CONFIG, useValue: cashewConfig(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)
HttpCacheInterceptorModule.forRoot({
  strategy: 'explicit'
});

ttl

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

HttpCacheInterceptorModule.forRoot({
  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:

HttpCacheInterceptorModule.forRoot({
  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;
}

Contributors ✨

Thanks go to these wonderful people (emoji key):


Netanel Basal

πŸ’» 🎨 πŸ“– πŸ€” πŸš‡

Itay Oded

πŸ’»

Shahar Kazaz

πŸ’»

Lars Gyrup Brink Nielsen

πŸ“–

RaΓ­ Siqueira

πŸ–‹

Inbal Sinai

πŸ’» πŸ“–

James Manners

πŸ’»

mokipedia

πŸ’» πŸ“–

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

More Repositories

1

falso

All the Fake Data for All Your Real Needs πŸ™‚
TypeScript
3,098
star
2

spectator

🦊 πŸš€ A Powerful Tool to Simplify Your Angular Tests
TypeScript
2,068
star
3

transloco

πŸš€ 😍 The internationalization (i18n) library for Angular
TypeScript
1,856
star
4

until-destroy

🦊 RxJS operator that unsubscribe from observables on destroy
TypeScript
1,733
star
5

elf

πŸ§™β€β™€οΈ A Reactive Store with Magical Powers
TypeScript
1,527
star
6

content-loader

βšͺ️ SVG component to create placeholder loading, like Facebook cards loading.
TypeScript
733
star
7

hot-toast

🍞 Smoking hot Notifications for Angular. Lightweight, customizable and beautiful by default.
TypeScript
687
star
8

reactive-forms

(Angular Reactive) Forms with Benefits πŸ˜‰
TypeScript
609
star
9

tailwind

πŸ”₯ A schematic that adds Tailwind CSS to Angular applications
TypeScript
608
star
10

query

πŸš€ Powerful asynchronous state management, server-state utilities and data fetching for Angular Applications
TypeScript
555
star
11

forms-manager

πŸ¦„ The Foundation for Proper Form Management in Angular
TypeScript
517
star
12

error-tailor

πŸ¦„ Making sure your tailor-made error solution is seamless!
TypeScript
478
star
13

helipopper

🚁 A Powerful Tooltip and Popover for Angular Applications
TypeScript
392
star
14

nx-serverless

πŸš€ The Ultimate Monorepo Starter for Node.js Serverless Applications
TypeScript
388
star
15

dialog

πŸ‘» A simple to use, highly customizable, and powerful modal for Angular Applications
TypeScript
371
star
16

hotkeys

πŸ€– A declarative library for handling hotkeys in Angular applications
TypeScript
325
star
17

edit-in-place

A flexible and unopinionated edit in place library for Angular applications
TypeScript
252
star
18

svg-icon

πŸ‘» A lightweight library that makes it easier to use SVG icons in your Angular Application
TypeScript
251
star
19

inspector

πŸ•΅οΈ An angular library that lets you inspect and change Angular component properties
TypeScript
218
star
20

dirty-check-forms

🐬Detect Unsaved Changes in Angular Forms
TypeScript
199
star
21

input-mask

🎭 @ngneat/input-mask is an angular library that creates an input mask
TypeScript
199
star
22

lib

πŸ€– Lets you focus on the stuff that matters
TypeScript
180
star
23

transloco-keys-manager

πŸ¦„ The Key to a Better Translation Experience
TypeScript
174
star
24

dag

🐠 An Angular service for managing directed acyclic graphs
TypeScript
153
star
25

bind-query-params

Sync URL Query Params with Angular Form Controls
TypeScript
147
star
26

from-event

🦊 ViewChild and FromEvent β€” a Match Made in Angular Heaven
TypeScript
137
star
27

overview

πŸ€– A collection of tools to make your Angular views more modular, scalable, and maintainable
TypeScript
118
star
28

aim

Angular Inline Module Schematics
TypeScript
97
star
29

cmdk

Fast, composable, unstyled command menu for Angular. Directly inspired from pacocoursey/cmdk
TypeScript
91
star
30

copy-to-clipboard

βœ‚οΈ Modern copy to clipboard. No Flash.
TypeScript
78
star
31

variabless

JS & CSS - A Match Made in Heaven πŸ’Ž
HTML
78
star
32

loadoff

🀯 When it comes to loaders, take a load off your mind...
TypeScript
78
star
33

effects

πŸͺ„ A framework-agnostic RxJS effects implementation
TypeScript
63
star
34

avvvatars

Beautifully crafted unique avatar placeholder for your next angular project.
TypeScript
46
star
35

react-rxjs

πŸ”Œ "Plug and play" for Observables in React Apps!
TypeScript
37
star
36

subscribe

Subscription Handling Directive
TypeScript
34
star
37

elf-ng-router-store

Bindings to connect Angular router to Elf
TypeScript
24
star
38

ng-standalone-nx

TypeScript
24
star
39

lit-file-generator

🎁 A lit generator for a component, directive, and controller.
JavaScript
19
star
40

storage

TypeScript
18
star
41

material-schematics

TypeScript
3
star
42

svg-icon-demo

TypeScript
1
star