HttpRequestCache
TS decorator for caching logic of API calls.
Inspired by How to use TS decorators to add caching logic to API calls
Installation
npm install @kovalenko/http-request-cache
Supported API
@HttpRequestCache<T>((thisObj?: T, ...args: any[]) => HttpCacheOptions)
— A method decorator that will cache data
thisObj
is this object
args
are decorated method's arguments
interface HttpCacheOptions {
refCount?: boolean; // If `refCount` is true, the source will be unsubscribed from once the reference count drops to zero
windowTime?: number; // Maximum time length of the replay buffer in milliseconds
ttl?: number; // cache time to live before it will be refreshed. Unlike refreshOn: interval(1000), ttl will not refresh automatically
storage?: HttpCacheStorage; // if none specified, the default cache object will be used
refreshOn?: Observable<unknown> | Subject<unknown> | BehaviorSubject<unknown>; // refresh trigger
}
export interface HttpCacheStorage {
setItem(key: string, item: Observable<any>): void; // sets cache
getItem(key: string): Observable<any> | undefined; // gets cache
deleteItem(key: string): void; // deletes cache
}
Usage
Default cache storage
@Injectable()
export class DataService {
refresh$ = new Subject();
constructor(private http: HttpClient) { }
@HttpRequestCache<DataService>(dataService => ({
refreshOn: dataService.refresh$
}))
list(): Observable<any> {
return this.http.get('assets/angular.json');
}
}
Unsubscribe internal ReplaySubject when there are no subscribers
@Injectable()
export class DataService {
constructor(private http: HttpClient) { }
@HttpRequestCache<DataService>(dataService => ({
refCount: true // set refCount to true to unsubscribe cache
}))
list(): Observable<any> {
return this.http.get('assets/angular.json');
}
}
Custom cache storage
@Injectable()
export class DataService {
refresh$ = new Subject();
constructor(private http: HttpClient, private cacheService: CacheService) { }
@HttpRequestCache<DataService>(dataService => ({
storage: dataService.cacheService,
refreshOn: dataService.refresh$
}))
list(): Observable<any> {
return this.http.get('assets/angular.json');
}
}
Parametrized refresh
@Injectable()
export class DataService {
refresh$ = new Subject<string>();
constructor(private http: HttpClient) { }
@HttpRequestCache<DataService>((dataService, id: string) => ({
refreshOn: dataService.refresh$.pipe(filter(r => r === id))
}))
list(id: string): Observable<any> {
return this.http.get('assets/angular.json');
}
}
TTL
When someone subscribes list() method after TTL period, every subscription is being refreshed
@Injectable()
export class DataService {
constructor(private http: HttpClient) { }
@HttpRequestCache(() => ({
ttl: 5000
}))
list(): Observable<any> {
return this.http.get('assets/angular.json');
}
}
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'],
})
export class AppComponent implements OnInit {
constructor(
private http: HttpClient,
private dataService: DataService
) { }
ngOnInit(): void {
this.dataService.list().subscribe(g => {
console.log('1', g);
});
// outputs:
// 1 {...}
setTimeout(() => {
this.dataService.list().subscribe(g => {
console.log('2', g);
});
}, 1000);
// outputs:
// 2 {...}
setTimeout(() => {
this.dataService.list().subscribe(g => {
console.log('3', g);
});
}, 6000);
// outputs:
// 1 {...}
// 2 {...}
// 3 {...}
setTimeout(() => {
this.dataService.list().subscribe(g => {
console.log('4', g);
});
}, 8000);
// outputs:
// 4 {...}
}
}
If cache invalidation is not necessary
@Injectable()
export class DataService {
constructor(private http: HttpClient) { }
@HttpRequestCache()
list(): Observable<any> {
return this.http.get('assets/angular.json');
}
}