2016-09-21 61 views
17

我有一個角2的服務,從API 這項服務3名的訂戶(在組件中所定義的)取數據各做與數據別的東西(不同的圖形)角2可觀察到與多個訂戶

我注意到我正在對API執行三個GET請求,而我想實現的是一個請求並且訂閱者將共享數據 我已經研究了HOT和COLD observable,並嘗試了可觀察對象上的.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

我遇到了類似的問題,並使用阿蘭的建議,參考科裏Rylan的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上調用next()方法時,都會更新訂閱者。

+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

您在代碼中存在的問題是每次調用函數時都返回一個新的observable。這是因爲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; 
    } 
}