Giter VIP home page Giter VIP logo

cashew's Introduction

Caching is nut a problem!


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

Features

βœ… HTTP Caching
βœ… Local Storage Support
βœ… Handles Simultaneous Requests
βœ… Automatic & Manual Cache Busting
βœ… Hackable

A flexible and straightforward library that caches HTTP requests in Angular

Buy Me A Coffee

Installation

NPM

$ 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 call the withCache function, and it'll cache the response:

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { withCache } from '@ngneat/cashew';

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

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

It's as simple as that.

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.

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.

Important note: View Engine users - instead of adding the config to the forRoot() method, add it in the app module providers in the following manner, using the supplied cashewConfig() method:

{ 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'
});

localStorageKey

When using local storage for caching, this defines the key where the cache is stored (for ttl - with the "Ttl" suffix): (defaults to 'httpCache')

HttpCacheInterceptorModule.forRoot({
  localStorageKey: string
});

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);
  }
});

parameterCodec

Define the HttpParameterCodec implementation if you need a different parameter encoder.

Example of custom implementation that uses encodeURIComponent:

import { HttpCacheInterceptorModule, useHttpCacheLocalStorage } from '@ngneat/cashew';
import { HttpParameterCodec } from '@angular/common/http';

class CustomHttpParameterCodec implements HttpParameterCodec {
  encodeKey(key: string): string {
    return encodeURIComponent(key);
  }
  encodeValue(value: string): string {
    return encodeURIComponent(value);
  }
  decodeKey(key: string): string {
    return decodeURIComponent(key);
  }
  decodeValue(value: string): string {
    return decodeURIComponent(value);
  }
}

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

or per request:

class CustomHttpParameterCodec implements HttpParameterCodec {
  ...
}

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

  getUsers() {
    return this.http.get(
      'api/users',
      withCache({
        parameterCodec$: new CustomHttpParameterCodec(),
        ...
      })
    );
  }
}

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
@Injectable()
export class UsersService {
  constructor(private http: HttpClient) {}

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

In addition to that, you can pass any query parameter that you need:

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

  getUser(id) {
    return this.http.get(
      'api/users',
      withCache({
        id,
        ttl$: 40000
      })
    );
  }
}

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 | RegExp | 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}`,
      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)
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 | RegExp): void;
}
  • KeySerializer - Generate the cache key based on the request: (defaults to request.urlWithParams)
export abstract class KeySerializer {
  abstract serialize(request: HttpCacheRequest): string;
}
  • HttpCacheGuard - When using the implicit strategy it first verifies that canActivate is truthy:
export abstract class HttpCacheGuard {
  abstract canActivate(request: HttpCacheRequest): 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 | RegExp): void;
}

Contributors ✨

Thanks goes 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!

cashew's People

Contributors

itayod avatar jmannau avatar layzeedk avatar mokipedia avatar netanelbasal avatar raisiqueira avatar shaharkazaz avatar theblushingcrow avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.