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
