web-dev-qa-db-fra.com

Angular 2 Observable avec plusieurs abonnés

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

36
naoru

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.

30
Scott

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;
   }
}
3
Ray Suelzer

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

1
Aran Dekar

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;
   }
}
0
zeliboba
0
Andre