web-dev-qa-db-fra.com

Angular 2 cache des données de résultats http observables

J'ai un service qui récupère des données via le service HTTP et retourne un objet observable.

Après le premier appel, je voudrais mettre en cache le résultat en interne dans le service, et une fois qu'un nouveau composant essaiera d'obtenir les données, il le prendra du résultat mis en cache.

Existe-t-il une solution simple à cela?

15
Avi

Si vous vous penchez sur les observables pour partager des données, vous pouvez adopter l'approche suivante:

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

Cela fera un appel HTTP lorsque la méthode fetch sera appelée, et tout abonné à service.data$ Obtiendra la réponse du ReplaySubject. Comme il rejoue les valeurs précédentes, tous les abonnés qui rejoignent après la résolution de l'appel HTTP obtiendront toujours la réponse précédente.

Si vous souhaitez déclencher une mise à jour, vous pouvez simplement appeler service.fetch() pour lancer un nouvel appel HTTP et tous les abonnés seront mis à jour une fois la nouvelle réponse arrivée.

Vos composants ressembleraient alors à quelque chose comme:

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

  constructor(private service: CachedService) { }

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

J'ai récemment écrit un article de blog sur cette approche pour mes collègues: http://blog.jonrshar.pe/2017/Apr/09/async-angular-data.html

47
jonrsharpe

Je pense que vous ne devriez pas faire de fetch () dans le constructeur ou à tout moment dans le cycle de vie d'angular. Et comme vous le dites, ngOnInit ne fonctionne pas dans les services angular.

Au lieu de cela, nous voulons tirer parti de rxjs pour nous transmettre de manière transparente les valeurs mises en cache via le flux - sans que l'appelant ait à connaître quoi que ce soit sur les valeurs mises en cache par rapport aux valeurs non mises en cache.

Si un composant a besoin de données, il y souscrit, qu'il s'agisse de cache ou non. Pourquoi voudriez-vous récupérer () des données dont vous n'êtes pas sûr qu'elles seront utilisées?

Le cache doit être implémenté à un niveau supérieur. Je pense que ce type d'implémentation est un bon début: 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;
}

Je ne suis pas sûr que ce soit le meilleur moyen, mais il est plus facile à entretenir car il a une seule responsabilité. Les données ne seront mises en cache que si un composant y souscrit, quel que soit/qui/quel composant a besoin des données et soit le premier à en avoir besoin.

9
Stefdelec

Vous pouvez créer une classe simple Cacheable <> qui aide à gérer le cache des données récupérées à partir du serveur http ou de toute autre 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();
    }

}

Utilisation

Déclarez l'objet Cacheable <> (probablement dans le cadre du service):

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

et gestionnaire:

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

Appel depuis un composant:

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

Plus de détails et un exemple de code sont ici: http://devinstance.net/articles/20171021/rxjs-cacheable

2
yfranz