web-dev-qa-db-ja.com

Angular 2キャッシュの観測可能なHTTP結果データ

HTTPサービスを介してデータを取得し、監視可能なオブジェクトを返すサービスがあります。

最初の呼び出しの後、結果をサービスに内部的にキャッシュし、新しいコンポーネントがデータを取得しようとすると、キャッシュされた結果から取得します。

これに簡単な解決策はありますか?

15
Avi

データを共有する手段としてオブザーバブルに頼る場合、次のアプローチを採用できます。

_import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';

import { Observable, ReplaySubject } from 'rxjs';

@Injectable()
export class CachedService {
  data$: Observable<Response> = this.dataSubject.asObservable();

  private dataSubject = new ReplaySubject<Response>(1);

  constructor(private http: Http) { }

  fetch() {
    this.http.get(...).subscribe(res => this.dataSubject.next(res));
  }
}
_

これはfetchメソッドが呼び出されたときにHTTP呼び出しを行い、_service.data$_のサブスクライバーはReplaySubjectから応答を取得します。以前の値を再生するため、HTTPコールの解決後にafterに参加したサブスクライバーは、以前の応答を引き続き受け取ります。

更新をトリガーする場合は、service.fetch()を呼び出すだけで新しいHTTP呼び出しを開始でき、新しい応答が到着するとすべてのサブスクライバーが更新されます。

コンポーネントは次のようになります。

_@Component({ ... })
export class SomeComponent implements OnInit {

  constructor(private service: CachedService) { }

  ngOnInit() {
    this.service.fetch();
    this.service.data$.subscribe(...);
  }
}
_

私は最近、同僚のためにこのアプローチに関するブログ記事を書きました: http://blog.jonrshar.pe/2017/Apr/09/async-angular-data.html

47
jonrsharpe

私はあなたがコンストラクタで、またはいつでもangularのライフサイクルの中でfetch()をすべきではないと思います。そしてあなたが言うように、ngOnInitはangularサービスでは機能しません。

代わりに、rxjsを活用して、キャッシュされた値をストリームにシームレスに渡します。呼び出し側は、キャッシュされた値とキャッシュされていない値について何も知る必要がありません。

コンポーネントがデータを必要とする場合、キャッシュかどうかに関係なく、サブスクライブします。使用するかどうかわからないデータをなぜfetch()するのですか?

キャッシュはより高いレベルで実装する必要があります。この種の実装は良いスタートだと思います: http://www.syntaxsuccess.com/viewarticle/caching-with-rxjs-observables-in-angular-2.

getFriends(){
    if(!this._friends){
      this._friends = this._http.get('./components/rxjs-caching/friends.json')
                                   .map((res:Response) => res.json().friends)
                                   .publishReplay(1)
                                   .refCount();
    }
    return this._friends;
}

それが最善の方法であるかどうかはわかりませんが、単一の責任があるため、保守が容易です。データがキャッシュされるのは、コンポーネントがデータを必要とし、最初に必要とするものに関係なく、コンポーネントがそれにサブスクライブする場合のみです。

9
Stefdelec

Httpサーバーまたはその他のソースから取得したデータのキャッシュの管理に役立つ単純なクラスCacheable <>を構築できます。

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

}

使用法

Cacheable <>オブジェクトを宣言します(おそらくサービスの一部として):

list: Cacheable<string> = new Cacheable<string>();

およびハンドラー:

this.list.getHandler = () => {
// get data from server
return this.http.get(url)
.map((r: Response) => r.json() as string[]);
}

コンポーネントからの呼び出し:

//gets data from server
List.getData().subscribe(…)

詳細とコード例はこちら: http://devinstance.net/articles/20171021/rxjs-cacheable

2
yfranz