2016-09-21 4 views
17

у меня есть угловой 2 службы, извлекающих данные из API эта услуга 3 подписчика (определенной в компонентах) каждый делает что-то еще с данными (различные графики)Угловое 2 Наблюдаемые с несколькими абонентами

Я заметив, что я делаю три запроса GET для API, в то время как то, что я хочу достичь, - это один запрос и что подписчики будут делиться данными. . Я просматриваю HOT и COLD, наблюдая и пробовал .share() на наблюдаемом, но Я все еще делаю 3 индивидуальных звонка

Обновление, добавление кода

служба

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')); 
    } 
} 

Компонент 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 
    ); 
    } 

Другие два компонента почти одинаковы, они просто показывают другой график

ответ

20

я столкнулся с подобной проблемой и решить ее с помощью предложения Арана, чтобы ссылаться на Angular 2 Observable Data Services блоге Кори Райлана в. Ключом для меня было использование BehaviorSubject. Вот фрагменты кода, которые в конечном итоге сработали для меня.

Служба данных:

Служба данных создает внутренний объект BehaviorSubject для кэширования данных один раз, когда служба инициализируется. Потребители используют метод subscribeToDataService() для доступа к данным.

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 

    } 
Компонент:

Компоненты могут подписаться на услугу передачи данных при инициализации.

export class TestComponent implements OnInit { 
     allData$: Observable<Data[]>; 

     constructor(private dataService: DataService) { 
     } 

     ngOnInit() { 
     this.allData$ = this.dataService.subscribeToDataService(); 
     } 

    } 
Компонент Шаблон:

Шаблон может затем итерацию по наблюдаемой в случае необходимости с помощью асинхронной трубы.

*ngFor="let data of allData$ | async" 

Абоненты обновляются каждый раз, когда метод следующий() вызывается на BehaviorSubject в службе данных.

+0

Благодаря вашему код действительно помог мне – naoru

+0

я потянув меня за волосы об этом «вопросе» и этот пост сделал мой день. Спасибо – sjosen

+0

Пожалуйста, рассмотрите более короткое решение: https://stackoverflow.com/questions/39627396/angular-2-observable-with-multiple-subscribers/48803113#48803113 – zeliboba

1

вы можете создать реактивную услугу передачи данных и определить локальную переменную Observable, которая обновляется внутренне и subscri могут обновить себя. эта статья объясняет это правильно data services

1

Проблема, которую вы имеете в своем коде, заключается в том, что вы возвращаете новое наблюдаемое при каждом вызове функции. Это связано с тем, что http.get создает новый Observable каждый раз, когда он вызывается. Способ решения проблемы может заключаться в том, чтобы хранить наблюдаемые (через закрытие) услуги, которые гарантируют, что все участники подписываются на одно и то же наблюдаемое. Это не идеальный код, но у меня была аналогичная проблема, и на данный момент это решило мою проблему.

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; 
    } 
} 
+0

Этот ответ заслуживает доверия, спасибо. Но, я думаю, вы должны объяснить свое использование '.share()', так как это имеет решающее значение для вопроса об избежании дублирования вызовов. Ответ хороший, если вы хотите избежать асинхронных вызовов одной и той же конечной точки API. Принимая во внимание, что принятый ответ может обеспечить кешированный ответ еще дольше, чем продолжительность бэкэнд-звонка. –

+0

Да, это не отличное решение. Принятый ответ намного лучше. –

0

Решение сохраняется после создания наблюдаемого и делает его доступным (по умолчанию это не так).Таким образом, ваша служба будет выглядеть следующим образом:

@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; 
    } 
} 
Смежные вопросы