Angular 5缓存http服务api调用

时间:2018-04-12 13:31:01

标签: angular angular-httpclient

在我的Angular 5应用程序中,在应用程序的不同位置需要多次某个数据集(不经常更改)。调用API后,结果将与Observable do运算符一起存储。这样我就可以在我的服务中实现HTTP请求的缓存。

我正在使用Angular 5.1.3和RxJS 5.5.6。

这是一个好习惯吗? 还有更好的选择吗?

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/do';

@Injectable()
export class FruitService {

  fruits: Array<string> = [];

  constructor(private http: HttpClient) { }

  getFruits() {
    if (this.fruits.length === 0) {
      return this.http.get<any>('api/getFruits')
        .do(data => { this.fruits = data })
    } else {
      return Observable.of(this.fruits);
    }
  }
}

5 个答案:

答案 0 :(得分:13)

您的解决方案存在的问题是,如果第一个呼叫在第一个呼叫挂起时发出,则会创建一个新的http请求。我将如何做到这一点:

@Injectable()
export class FruitService {

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

  constructor(private http: HttpClient) { }
}

更大的问题是当你有params而你想根据params缓存。在这种情况下,您需要某种memoize函数,例如来自lodash(https://lodash.com/docs/4.17.5#memoize)的函数

您还可以为cache实现一些内存Observable运算符,例如:

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;

并使用它:

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

答案 1 :(得分:2)

实际上,缓存响应并共享单个订阅(不为每个订阅者提出新请求)的最简单方法是使用publishReplay(1)refCount()(我使用pipable运算符)

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

然后,当您想要获得缓存/新鲜值时,您只需订阅fresh$

fresh$.subscribe(...)

publishReplay运算符缓存该值,然后refCount仅维护其父级的一个订阅,并且如果没有订阅者则取消订阅。 take(1)是在单个值之后正确完成链的必要条件。

最重要的部分是,当您订阅此链时publishReplay会在订阅时发出其缓冲区,如果它包含缓存值,它会立即传播到完成链的take(1)所以它根本不会创建this.http.get的订阅。如果publishReplay不包含任何内容,则会订阅其来源并发出HTTP请求。

答案 2 :(得分:2)

还有另一种方法与shareReplay和Angular 5、6或7一起创建服务:

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).pipe();
}

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

要直接在html文件中读取数据,请使用以下方法:

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

在您的组件中,您可以像这样订阅:

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

答案 3 :(得分:1)

对于Angular 6,RxJS 6和简单的缓存过期,请使用以下代码:

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);
      }
    });
  };
}

答案 4 :(得分:0)

根据其他一些答案,如果 API 调用具有参数(例如 kindcolor),这里有一个简单的变体。

import { HttpClient, HttpParams } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { shareReplay } from 'rxjs/operators';

@Injectable()
export class FruitService {
  private readonly cache: Map<string, Observable<string[]>> =
    new Map<string, Observable<string[]>>();

  constructor(private readonly httpClient: HttpClient) {}

  getFruits(kind: string, color: string): Observable<string[]> {
    const key = `${kind}${color}`;
    if (!this.cache[key]) {
      this.cache[key] = this.httpClient
        .get<string[]>('api/getFruits', { params: { kind, color} })
        .pipe(shareReplay(1));
    }
    return this.cache[key];
  }
}