Angular 5 caching http service api calls

半世苍凉 提交于 2019-11-28 09:17:58

The problem with your solution is that if a 2nd call comes while a 1st one is pending, it create a new http request. Here is how I would do it:

@Injectable()
export class FruitService {

  readonly fruits = this.http.get<any>('api/getFruits').shareReplay(1);

  constructor(private http: HttpClient) { }
}

the bigger problem is when you have params and you want to cache based on the params. In that case you would need some sort of memoize function like the one from lodash (https://lodash.com/docs/4.17.5#memoize)

You can also implement some in-memory cache operator for the Observable, like:

const cache = {};

function cacheOperator<T>(this: Observable<T>, key: string) {
    return new Observable<T>(observer => {
        const cached = cache[key];
        if (cached) {
            cached.subscribe(observer);
        } else {
            const add = this.multicast(new ReplaySubject(1));
            cache[key] = add;
            add.connect();
            add.catch(err => {
                delete cache[key];
                throw err;
            }).subscribe(observer);
        }
    });
}

declare module 'rxjs/Observable' {
    interface Observable<T> {
        cache: typeof cacheOperator;
    }
}

Observable.prototype.cache = cacheOperator;

and use it like:

getFruit(id: number) {
  return this.http.get<any>(`api/fruit/${id}`).cache(`fruit:${id}`);
}

Actually, the easiest way of caching responses and also sharing a single subscription (not making a new request for every subscriber) is using publishReplay(1) and refCount() (I'm using pipable operators).

readonly fruits$ = this.http.get<any>('api/getFruits')
  .pipe(
    publishReplay(1), // publishReplay(1, _time_)
    refCount(),
    take(1),
  );

Then when you want to get the cached/fresh value you'll just subscribe to fresh$.

fresh$.subscribe(...)

The publishReplay operator caches the value, then refCount maintains only one subscription to its parent and unsubscribes if there are no subscribers. The take(1) is necessary to properly complete the chain after a single value.

The most important part is that when you subscribe to this chain publishReplay emits its buffer on subscription and if it contains a cached value it'll be immediately propagated to take(1) that completes the chain so it won't create subscription to this.http.get at all. If publishReplay doesn't contain anything it'll subscribe to its source and make the HTTP request.

D3F

There is another way doing this with shareReplay and Angular 5, 6 or 7 : create a Service :

import { Observable } from 'rxjs/Observable';
import { shareReplay } from 'rxjs/operators';
const CACHE_SIZE = 1;

private cache$: Observable<Object>;

get api() {
  if ( !this.cache$ ) {
    this.cache$ = this.requestApi().pipe( shareReplay(CACHE_SIZE) );
  }
  return this.cache_arbitrage$;
}

private requestApi() {
  const API_ENDPOINT = 'yoururl/';
  return this.http.get<any>(API_ENDPOINT_ARBITRATION);
}

public resetCache() {
  this.cache$ = null;
}

To read the data directly in your html file use this :

<div *ngIf="this.apiService.api | async as api">{{api | json}}</div>

In your component you can subscribe like this:

this.apiService.api.subscribe(res => {/*your code*/})

For Angular 6, RxJS 6 and simple cache expiration use the following code:

interface CacheEntry<T> {
  expiry: number;
  observable: Observable<T>;
}

const DEFAULT_MAX_AGE = 300000;
const globalCache: { [key: string]: CacheEntry<any>; } = {};

export function cache(key: string, maxAge: number = DEFAULT_MAX_AGE) {
  return function cacheOperatorImpl<T>(source: Observable<T>) {
    return Observable.create(observer => {
      const cached = globalCache[key];
      if (cached && cached.expiry >= Date.now()) {
        cached.observable.subscribe(observer);
      } else {
        const add = source.pipe(multicast(new ReplaySubject(1))) as ConnectableObservable<T>;
        globalCache[key] = {observable: add, expiry: Date.now() + maxAge};
        add.connect();
        add.pipe(
          catchError(err => {
            delete globalCache[key];
            return throwError(err);
          })
        ).subscribe(observer);
      }
    });
  };
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!