Angular - best way to cache http response
Asked Answered
P

7

16

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?

Proprietor answered 4/10, 2017 at 14:51 Comment(2)
There's a section about caching in the HttpClient documentation. What have you tried so far and what didn't work for you? angular.io/guide/http#cachingAlysonalysoun
Thank's for response!Proprietor
P
7

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

Pich answered 27/11, 2017 at 18:50 Comment(1)
Thanks for posting the links as part of your answer.Soloist
Q
4

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.

Quarter answered 2/8, 2023 at 17:8 Comment(0)
S
2

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

Shiv answered 16/6, 2020 at 11:0 Comment(0)
W
0

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>
Witkin answered 27/6, 2022 at 2:0 Comment(0)
K
0

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();
  }
}
Kurtzig answered 4/8, 2023 at 8:23 Comment(0)
U
0
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));
   }
}
Universality answered 30/5, 2024 at 9:10 Comment(0)
B
0

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;
      })
    );
  }
 
}
Batty answered 16/7, 2024 at 8:21 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.