I have a lot of services with requests to rest service and I want to cache data receive from server for further usage. Could anyone tell what is the best way to cash response?
You will find multiple answers here: Angular 2 cache observable http result data
I would recommend to build simple class Cacheable<> that helps managing cache of data retrieved from http server or other any other source:
declare type GetDataHandler<T> = () => Observable<T>;
export class Cacheable<T> {
protected data: T;
protected subjectData: Subject<T>;
protected observableData: Observable<T>;
public getHandler: GetDataHandler<T>;
constructor() {
this.subjectData = new ReplaySubject(1);
this.observableData = this.subjectData.asObservable();
}
public getData(): Observable<T> {
if (!this.getHandler) {
throw new Error("getHandler is not defined");
}
if (!this.data) {
this.getHandler().map((r: T) => {
this.data = r;
return r;
}).subscribe(
result => this.subjectData.next(result),
err => this.subjectData.error(err)
);
}
return this.observableData;
}
public resetCache(): void {
this.data = null;
}
public refresh(): void {
this.resetCache();
this.getData();
}
}
Usage
Declare Cacheable<> object (presumably as part of the service):
list: Cacheable<string> = new Cacheable<string>();
and handler:
this.list.getHandler = () => {
// get data from server
return this.http.get(url)
.map((r: Response) => r.json() as string[]);
}
Call from a component:
//gets data from server
List.getData().subscribe(…)
More details and code example are here: http://devinstance.net/articles/20171021/rxjs-cacheable
The easiest way to cache the data inside a service is to involve shareReplay()
example.service.ts
import { shareReplay } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { HttpClient } from '@angular/common/http';
@Injectable({providedIn: 'root'})
export class ExampleService {
private readonly _data: Observable<IExampleModel[]>;
constructor(private readonly http: HttpClient) {
this._data =
this.http.get<IExampleModel[]>('/api/example-entities/')
.pipe(shareReplay());
}
getExampleEntities() : Observable<IExampleModel[]> {
return this._data;
}
}
An alternative widely used approach with ReplaySubject<T>(1)
could be found here.
You can also use http interceptors with angular 4+ versions. A very detailed and straight-forward implementation with explanation-https://www.concretepage.com/angular/angular-caching-http-interceptor
For GET responses, you can try using this get shell component, which is super easy to use and set the cache configurations. ngx-http-get-shell
<ngx-http-get-shell [url]="url" [maxStale]="1000" [templateRef]="tmpl">
<ng-template #tmpl let-data>
{{ data | json }}
</ng-template>
</ngx-http-get-shell>
Just my two cents using ReplaySubject and race
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { race, ReplaySubject, tap } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable()
export class RandomNumberService {
constructor(private http: HttpClient) {}
#number$ = this.newCache;
private get newCache() {
return new ReplaySubject<number>(1);
}
clearCache() {
this.#number$ = this.newCache;
}
getNumber() {
return this.http
.get<{ number: number }>(
'/api/random-number'
)
.pipe(map(({ number }) => number));
}
getNumberCachedVersion() {
return race(
this.#number$,
this.getNumber().pipe(tap((number) => this.#number$.next(number)))
);
}
getNumberCachedVersionWithRefreshParameter(refresh: boolean = false) {
if (refresh) {
this.clearCache();
}
return this.getNumberCachedVersion();
}
}
import { shareReplay } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { HttpClient } from '@angular/common/http';
@Injectable({providedIn: 'root'})
export class ProductService {
private _producs$?: Observable<BankProduct[]>;
public get producs$(): Observable<BankProduct[]> {
if (!this._producs$) {
this._producs$ = this.getAll();
}
return this._producs$;
}
constructor(private readonly http: HttpClient) {
}
private getAll() {
return this.http.get<Product[]>('/api/product/')
.pipe(shareReplay(1));
}
}
This is quite a simple solution when ever the get method is called it will just next the value into a BehaviorSubject within the service. Then other components could read from that cached value.
@Injectable({
providedIn: 'root',
})
export class ProjectService {
constructor(protected http: HttpClient) {}
cachedProject = new BehaviorSubject<Project>(undefined);
getProject(projectId: string): Observable<Project> {
const url = `/projects/${projectId}`;
return this.http.get(url).pipe(
map((project: Project) => {
this.cachedProject.next(project);
return project;
})
);
}
}
© 2022 - 2025 — McMap. All rights reserved.