Ich habe einen Winkel 2-Dienst, der Daten von einer API abruft. Dieser Dienst hat 3 Abonnenten (in Komponenten definiert), die jeweils etwas anderes mit den Daten tun (unterschiedliche Diagramme).
Ich stelle fest, dass ich drei GET-Anforderungen an die API durchführe, während ich eine Anforderung erreichen möchte und dass die Abonnenten die Daten freigeben werden. ) auf dem Observable, aber ich mache immer noch 3 einzelne Anrufe
Update, Code hinzufügen
Bedienung
import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import {Observable} from 'rxjs/Rx';
// Import RxJs required methods
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import { StationCompliance } from './model/StationCompliance';
@Injectable()
export class StationComplianceService {
private url = '/api/read/stations';
constructor(private http : Http) {
console.log('Started Station compliance service');
}
getStationCompliance() : Observable<StationCompliance []> {
return this.http.get(this.url)
.map((res:Response) => res.json())
.catch((error:any) => Observable.throw(error.json().error || 'Server Error'));
}
}
Komponente 1
import { Component, OnInit } from '@angular/core';
import { CHART_DIRECTIVES } from 'angular2-highcharts';
import { StationComplianceService } from '../station-compliance.service';
@Component({
selector: 'app-up-down-graph',
templateUrl: './up-down-graph.component.html',
styleUrls: ['./up-down-graph.component.css']
})
export class UpDownGraphComponent implements OnInit {
graphData;
errorMessage: string;
options;
constructor(private stationService : StationComplianceService) { }
ngOnInit() {
this.getStationTypes();
}
getStationTypes(){
this.stationService.getStationCompliance()
.subscribe(
graphData => {
this.graphData = graphData;
this.options = {
chart : {type: 'pie',
plotShadow: true
},
plotOptions : {
showInLegend: true
},
title : {text: 'Up and Down devices'},
series: [{
data: this.processStationType(this.graphData)
}]
}
},
error => this.errorMessage = <any>error
);
}
Die anderen beiden Komponenten sind fast gleich, sie zeigen nur andere Grafiken
Ich bin auf ein ähnliches Problem gestoßen und habe es mit Arans Vorschlag gelöst, auf Cory Rylans Angular 2 Observable Data Services Blogbeitrag zu verweisen. Der Schlüssel für mich war BehaviorSubject
. Hier sind die Ausschnitte des Codes, der letztendlich für mich funktioniert hat.
Der Datendienst erstellt ein internes BehaviorSubject
zum Zwischenspeichern der Daten, sobald der Dienst initialisiert wird. Verbraucher verwenden die subscribeToDataService()
-Methode, um auf die Daten zuzugreifen.
import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Observable } from 'rxjs/Observable';
import { Data } from './data';
import { properties } from '../../properties';
@Injectable()
export class DataService {
allData: Data[] = new Array<Data>();
allData$: BehaviorSubject<Data[]>;
constructor(private http: Http) {
this.initializeDataService();
}
initializeDataService() {
if (!this.allData$) {
this.allData$ = <BehaviorSubject<Data[]>> new BehaviorSubject(new Array<Data>());
this.http.get(properties.DATA_API)
.map(this.extractData)
.catch(this.handleError)
.subscribe(
allData => {
this.allData = allData;
this.allData$.next(allData);
},
error => console.log("Error subscribing to DataService: " + error)
);
}
}
subscribeToDataService(): Observable<Data[]> {
return this.allData$.asObservable();
}
// other methods have been omitted
}
Komponenten können bei der Initialisierung den Datendienst abonnieren.
export class TestComponent implements OnInit {
allData$: Observable<Data[]>;
constructor(private dataService: DataService) {
}
ngOnInit() {
this.allData$ = this.dataService.subscribeToDataService();
}
}
Die Vorlage kann dann mit Hilfe der asynchronen Pipe über das Beobachtbare iterieren.
*ngFor="let data of allData$ | async"
Abonnenten werden bei jedem Aufruf der next()
-Methode für BehaviorSubject
im Datendienst aktualisiert.
Das Problem, das Sie in Ihrem Code haben, besteht darin, dass Sie bei jedem Aufruf Ihrer Funktion ein neues Observable zurückgeben. Dies liegt daran, dass http.get
bei jedem Aufruf ein neues Observable erstellt. Die Lösung dieses Problems könnte darin bestehen, das Beobachtbare (durch Schließung) im Dienst zu speichern, wodurch sichergestellt wird, dass alle Probanden dasselbe Beobachtbare abonnieren. Dies ist kein perfekter Code, aber ich hatte ein ähnliches Problem und dies löste mein Problem vorerst.
import { Injectable } from '@angular/core';
import { Http, Response } from '@angular/http';
import {Observable} from 'rxjs/Rx';
// Import RxJs required methods
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import { StationCompliance } from './model/StationCompliance';
@Injectable()
export class StationComplianceService {
private url = '/api/read/stations';
constructor(private http : Http) {
console.log('Started Station compliance service');
}
private stationComplianceObservable: Rx.Observable<StationCompliance[]>;
getStationCompliance() : Observable<StationCompliance []> {
if(this.stationComplianceObservable){
return this.stationComplianceObservable;
}
this.stationComplianceObservable = this.http.get(this.url)
.debounce(1000)
.share()
.map((res:Response) => res.json())
.finally(function () { this.stationComplianceObservable = null})
.catch((error:any) => Observable.throw(error.json().error || 'Server Error'));
return this.stationComplianceObservable;
}
}
sie können einen reaktiven Datendienst erstellen und eine lokale beobachtbare Variable definieren, die intern aktualisiert wird und die Abonnenten selbst aktualisieren können. Dieser Artikel erklärt es richtig Datendienste
Ich weiß, dass dieser Thread alt ist, aber die akzeptierte Antwort hat mir sehr geholfen und ich möchte einige mögliche Verbesserungen hinzufügen, indem ich debounce, switchmap und ein hacky globales Benachrichtigungssystem verwende (dazu sollte das richtige ngrx verwendet werden: https: //ngrx.io/ )
Die Voraussetzung des Konzepts ist, dass ein Benachrichtigungsdienst verwendet werden kann, um alle anderen Dienste zu pushen, die sie auffordern, ihre Daten abzurufen:
export class NotifyerService {
constructor() { }
notifyer: Subject<any> = new Subject
notifyAll() {
console.log("ALL NOTIFIED")
this.notifyer.next("GO")
}
}
Ein Betreff wird verwendet, weil der Aufruf von .next (val) für einen Betreff die Daten an alle Listener weiterleitet
Im Service für eine bestimmte Komponente (in Ihrem Fall "DataService") können Sie die Datenerfassung und die Caching-Aktivität verwalten:
export class GetDataService {
// cache the incoming data
cache: Subject<any> = new Subject
constructor(private http: HttpClient,
private notifyerService: NotifyerService) {
// subscribe to any notification from central message broker
this.notifyerService.notifyer.pipe(
// filtering can be used to perform different actions based on different notifications
filter(val => val == "GO"),
// prevent notification spam by debouncing
debounceTime(300),
// SUBSCRIBE to the output of getData, cancelling previous subscription
switchMap(() => this.getData())
).subscribe(res => {
console.log("expensive server request")
// save data in cache, notify listeners
this.cache.next(res)
})
}
// expensive function which gets data
getData(): Observable<any> {
return this.http.get<any>(BASE_URL);
}
}
Das Schlüsselkonzept im obigen Code ist, dass Sie ein Cache-Objekt einrichten und aktualisieren, wenn eine Benachrichtigung vorliegt. Im Konstruktor möchten wir alle zukünftigen Benachrichtigungen über eine Reihe von Operatoren weiterleiten:
Danach werden die Serverdaten mit der .next (res) -Methode in den Cache gestellt.
Jetzt muss die letzte Komponente nur noch den Cache auf Aktualisierungen abhören und entsprechend vorgehen:
export class ButtonclickerComponent implements OnInit {
value: any;
constructor(private getDataService: GetDataService,
private notifyerService: NotifyerService) { }
ngOnInit() {
// listen to cache for updates
this.getDataService.cache.pipe(
// can do something specific to this component if have multiple subscriptions off same cache
map(x => x)
// subsc
).subscribe(x => { console.log(x); this.value = x.value })
}
onClick() {
// notify all components of state changes
this.notifyerService.notifyAll()
}
}
Das Konzept in Aktion:
Die Lösung wird gespeichert, sobald sie beobachtbar erstellt wurde, und sie kann gemeinsam genutzt werden (standardmäßig nicht). So wird Ihr Service aussehen:
@Injectable()
export class StationComplianceService {
private stationCompliance: StationCompliance;
private stream: Observable<StationCompliance []>;
private url = '/api/read/stations';
constructor(private http : Http) {
console.log('Started Station compliance service');
}
getStationCompliance() : Observable<StationCompliance []> {
/** is remote value is already fetched, just return it as Observable */
if (this.stationComliance) {
return Observable.of(this.stationComliance);
}
/** otherwise if stream already created, prevent another stream creation (exactly your question */
if (this.stream) {
return this.stream;
}
/** otherwise run remote data fetching */
this.stream = this.http.get(this.url)
.map((res:Response) => res.json())
.catch((error:any) => Observable.throw(error.json().error || 'Server Error'))
.share(); /** and make the stream shareable (by default it is not) */
return this.stream;
}
}