web-dev-qa-db-ja.com

Angular 5 HTTPサービスAPI呼び出しのキャッシュ

My 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);
    }
  }
}
10
Herman Fransen

ソリューションの問題は、1番目の呼び出しが保留中に2番目の呼び出しが来た場合、新しいhttp要求を作成することです。ここに私がそれをする方法があります:

@Injectable()
export class FruitService {

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

  constructor(private http: HttpClient) { }
}

大きな問題は、パラメーターがあり、そのパラメーターに基づいてキャッシュする場合です。その場合、lodashのようなmemoize関数が必要になります( 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}`);
}
14
Andrei Tătar

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

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

Htmlファイルのデータを直接読み取るには、次を使用します。

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

コンポーネントでは、次のようにサブスクライブできます。

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

実際、応答をキャッシュし、単一のサブスクリプションを共有する(すべてのサブスクライバーに新しいリクエストを作成しない)最も簡単な方法は、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はその親に対するサブスクリプションを1つだけ維持し、サブスクライバーがいない場合はサブスクリプションを解除します。 take(1)は、単一の値の後にチェーンを適切に完了するために必要です。

最も重要な部分は、このチェーンにサブスクライブすると、publishReplayがサブスクリプションでバッファを発行し、キャッシュされた値が含まれている場合、チェーンを完了するtake(1)にすぐに伝播されるためです。 _this.http.get_へのサブスクリプションをまったく作成しません。 publishReplayに何も含まれていない場合、そのソースにサブスクライブし、HTTPリクエストを作成します。

2
martin

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