ngneat / Cashew
Programming Languages
Projects that are alternatives of or similar to Cashew
Caching is nut a problem!
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
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 thewithCache
function -
implicit
- caches API requests that are of typeGET
and the response type isJSON
. You can change this behavior by overriding theHttpCacheGuard
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 totrue
) -
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 theHttpResponse
from the cache -
has(key: string)
- Returns aboolean
indicates whether the providedkey
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 torequest.urlWithParams
)
export abstract class KeySerializer {
abstract serialize(request: HttpCacheRequest): string;
}
-
HttpCacheGuard
- When using theimplicit
strategy it first verifies thatcanActivate
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!