Website-Suche

angular - Ergebnisse mit dem Angular2 HTTP-Dienst zwischenspeichern


Ich stelle eine HTTP-GET-Anfrage über einen Dienst bereit, und mehrere Komponenten verwenden diese Daten (Profildetails eines Benutzers). Ich möchte, dass die erste Komponentenanfrage tatsächlich die HTTP-GET-Anfrage an den Server durchführt und die Ergebnisse zwischenspeichert, sodass die nachfolgenden Anfragen die zwischengespeicherten Daten verwenden, anstatt den Server erneut aufzurufen.

Hier ist ein Beispiel für den Service, wie würden Sie empfehlen, diese Cache-Schicht mit Angular2 und TypeScript zu implementieren.

import {Inject, Injectable} from 'angular2/core';
import {Http, Headers} from "angular2/http";
import {JsonHeaders} from "./BaseHeaders";
import {ProfileDetails} from "../models/profileDetails";

@Injectable()
export class ProfileService{
    myProfileDetails: ProfileDetails = null;

    constructor(private http:Http) {

    }

    getUserProfile(userId:number) {
        return this.http.get('/users/' + userId + '/profile/', {
                headers: headers
            })
            .map(response =>  {
                if(response.status==400) {
                    return "FAILURE";
                } else if(response.status == 200) {
                    this.myProfileDetails = new ProfileDetails(response.json());
                    return this.myProfileDetails;
                }
            });
    }
}

Antwort #1

Der share()-Operator funktioniert nur beim ersten Abruf. Wenn alle Abonnements bedient sind und Sie ein weiteres erstellen, funktioniert er nicht mehr, es wird eine weitere Anfrage gestellt. (Dieser Fall ist ziemlich häufig, da Sie bei der Angular2-SPA immer Komponenten erstellen/zerstören)

Ich habe ein ReplaySubject verwendet, um den Wert aus dem http-Observable zu speichern. Das ReplaySubject-Observable kann seinen Abonnenten vorherige Werte bereitstellen.

der Service:

@Injectable()
export class DataService {
    private dataObs$ = new ReplaySubject(1);

    constructor(private http: HttpClient) { }

    getData(forceRefresh?: boolean) {
        // If the Subject was NOT subscribed before OR if forceRefresh is requested 
        if (!this.dataObs$.observers.length || forceRefresh) {
            this.http.get('http://jsonplaceholder.typicode.com/posts/2').subscribe(
                data => this.dataObs$.next(data),
                error => {
                    this.dataObs$.error(error);
                    // Recreate the Observable as after Error we cannot emit data anymore
                    this.dataObs$ = new ReplaySubject(1);
                }
            );
        }

        return this.dataObs$;
    }
}

die Komponente:

@Component({
  selector: 'my-app',
  template: `<div (click)="getData()">getData from AppComponent</div>`
})
export class AppComponent {
    constructor(private dataService: DataService) {}

getData() {
    this.dataService.getData().subscribe(
        requestData => {
            console.log('ChildComponent', requestData);
        },
        // handle the error, otherwise will break the Observable
        error => console.log(error)
    );
}
    }
}

voll funktionsfähiger PLUNKER
(beobachte die Konsole und den Netzwerk-Tab)

Antwort #2

Ich habe die userId-Verarbeitung weggelassen. Es würde erfordern, ein Array von data und ein Array von observable (jeweils eines für jede angeforderte userId) stattdessen zu verwalten.

import {Injectable} from '@angular/core';
import {Http, Headers} from '@angular/http';
import {Observable} from 'rxjs/Observable';
import 'rxjs/observable/of';
import 'rxjs/add/operator/share';
import 'rxjs/add/operator/map';
import {Data} from './data';

@Injectable()
export class DataService {
  private url:string = 'https://cors-test.appspot.com/test';

  private data: Data;
  private observable: Observable<any>;

  constructor(private http:Http) {}

  getData() {
    if(this.data) {
      // if `data` is available just return it as `Observable`
      return Observable.of(this.data); 
    } else if(this.observable) {
      // if `this.observable` is set then the request is in progress
      // return the `Observable` for the ongoing request
      return this.observable;
    } else {
      // example header (not necessary)
      let headers = new Headers();
      headers.append('Content-Type', 'application/json');
      // create the request, store the `Observable` for subsequent subscribers
      this.observable = this.http.get(this.url, {
        headers: headers
      })
      .map(response =>  {
        // when the cached data is available we don't need the `Observable` reference anymore
        this.observable = null;

        if(response.status == 400) {
          return "FAILURE";
        } else if(response.status == 200) {
          this.data = new Data(response.json());
          return this.data;
        }
        // make it shared so more than one subscriber can get the result
      })
      .share();
      return this.observable;
    }
  }
}

Plunker-Beispiel

Sie können eine weitere interessante Lösung unter https://stackoverflow.com/a/36296015/217408 finden

Antwort #3

Bezüglich Ihres letzten Kommentars, dies ist der einfachste Weg, an den ich denken kann: Erstellen Sie einen Dienst, der eine Eigenschaft haben wird, und diese Eigenschaft wird die Anfrage enthalten.

class Service {
  _data;
  get data() {
    return this._data;
  }
  set data(value) {
    this._data = value;
  }
}

So einfach ist das. Alles andere im plnkr würde unberührt bleiben. Ich habe die Anfrage vom Service entfernt, weil sie automatisch instanziiert wird (wir machen kein new Service..., und mir ist kein einfacher Weg bekannt, einen Parameter über den Konstruktor zu übergeben).

Also, jetzt haben wir den Service, was wir jetzt tun, ist, die Anfrage in unserer Komponente zu machen und sie der Service-Variable data zuzuweisen

class App {
  constructor(http: Http, svc: Service) {

    // Some dynamic id
    let someDynamicId = 2;

    // Use the dynamic id in the request
    svc.data = http.get('http://someUrl/someId/'+someDynamicId).share();

    // Subscribe to the result
    svc.data.subscribe((result) => {
      /* Do something with the result */
    });
  }
}

Denken Sie daran, dass unsere Service-Instanz für jede Komponente dieselbe ist, sodass, wenn wir einen Wert zu data zuweisen, dieser in jeder Komponente angezeigt wird.

Hier ist der Plnkr mit einem funktionierenden Beispiel.

Referenz

  • RxJS Share-Operator

Verwandte Artikel