J'ai un service 2 angulaire qui récupère les données d'une API Ce service a 3 abonnés (définis dans Composants), chacun faisant autre chose avec les données (graphiques différents).
Je remarque que je fais trois demandes GET à l'API alors que ce que je veux réaliser est une demande et que les abonnés partageront les données ) sur l'observable mais je fais encore 3 appels individuels
Mise à jour, ajout de code
Un service
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'));
}
}
Composante 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
);
}
Les deux autres composants sont presque identiques, ils montrent simplement un autre graphique
J'ai rencontré un problème similaire et je l'ai résolu en utilisant la suggestion d'Aran de faire référence à Cory Rylan Angular 2 Observable Data Services blog. La clé pour moi utilisait BehaviorSubject
. Voici les extraits du code qui ont finalement fonctionné pour moi.
Le service de données crée une variable BehaviorSubject
interne pour mettre en cache les données une fois le service initialisé. Les consommateurs utilisent la méthode subscribeToDataService()
pour accéder aux données.
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
}
Les composants peuvent s'abonner au service de données lors de l'initialisation.
export class TestComponent implements OnInit {
allData$: Observable<Data[]>;
constructor(private dataService: DataService) {
}
ngOnInit() {
this.allData$ = this.dataService.subscribeToDataService();
}
}
Le modèle peut ensuite parcourir l'observable selon les besoins à l'aide du canal asynchrone.
*ngFor="let data of allData$ | async"
Les abonnés sont mis à jour chaque fois que la méthode next()
est appelée sur BehaviorSubject
dans le service de données.
Le problème que vous avez dans votre code est que vous retournez une nouvelle observable chaque fois que votre fonction est appelée. En effet, http.get
crée un nouvel observable à chaque appel. La solution à ce problème pourrait consister à stocker l'observable (via la fermeture) dans le service, ce qui garantira que tous les sujets s'abonnent au même observable. Ce n'est pas un code parfait, mais j'ai eu un problème similaire et cela a résolu mon problème pour le moment.
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;
}
}
vous pouvez créer un service de données réactif et définir une variable observable locale qui est mise à jour en interne et les abonnés peuvent se mettre à jour eux-mêmes . cet article l'explique correctement services de données
La solution est sauvegardée une fois créée observable et rendue accessible (par défaut ce n’est pas) Donc, votre service ressemblera à:
@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;
}
}
shareReplay
devrait le faire maintenant - "(...) utile dans les situations où vous savez que vous aurez des abonnés en retard à un flux qui nécessite un accès aux valeurs précédemment émises."