具有多个订户的Angular 2 Observable

时间:2016-09-21 22:15:17

标签: angular rxjs

我有一个从API获取数据的角度2服务 此服务有3个订阅者(在“组件”中定义),每个订阅者使用数据(不同的图表)执行其他操作

我注意到我正在向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
      );
  }

其他两个组件几乎相同,只是显示其他图形

6 个答案:

答案 0 :(得分:32)

我遇到了类似的问题并使用Aran的建议来解决它,以引用Cory Rylan的Angular 2 Observable Data Services博客文章。我的关键是使用se = TestCase.settings;。以下是最终对我有用的代码片段。

数据服务:

数据服务创建内部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

    }
组件模板:

然后,模板可以根据需要使用异步管道迭代observable。

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

      constructor(private dataService: DataService) {
      }

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

    }

每次在数据服务中的 *ngFor="let data of allData$ | async" 上调用next()方法时,都会更新订阅者。

答案 1 :(得分:4)

您的代码中存在的问题是,每次调用函数时都会返回一个新的observable。这是因为http.get每次调用时都会创建一个新的Observable。解决这个问题的方法可能是在服务中存储observable(通过闭包),这将确保所有主题都订阅相同的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;
   }
}

答案 2 :(得分:1)

您可以创建一个被动数据服务并定义一个本地Observable变量,该变量在内部更新,订阅者可以自行更新。 本文正确解释了它 data services

答案 3 :(得分:0)

解决方案是一旦创建了observable就保存并使其可共享(默认情况下不是)。所以你的服务看起来像是:

@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;
   }
}

答案 4 :(得分:0)

答案 5 :(得分:0)

我知道这个线程很旧,但是被接受的答案对我有很大帮助,我想使用反跳,switchmap和hacky的全局通知系统添加一些可能的改进(应该使用适当的ngrx:{{3 }})

该概念的前提是可以使用通知服务将更改推送到所有其他服务,以告知它们获取数据:

export class NotifyerService {

  constructor() { }

  notifyer: Subject<any> = new Subject

  notifyAll() {
    console.log("ALL NOTIFIED")
    this.notifyer.next("GO")
  }

}

使用一个主题,因为在一个主题上调用.next(val)会将数据推送到所有侦听器

在特定组件的服务(在您的情况下为“ DataService”)中,您可以管理数据获取和缓存活动:

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

} 

以上代码中的关键概念是您设置一个缓存对象,并在有通知时对其进行更新。在构造函数中,我们希望通过一系列运算符通过管道传递所有将来的通知:

    如果仅在以下情况下要更新缓存,可以使用
  • 过滤: 通知服务输出某些内容(在这种情况下为“ GO”)。如果 您开始这样做时,几乎可以肯定会使用ngrx。
  • debounceTime可以防止服务器收到很多请求的垃圾邮件(例如,通知基于用户输入)
  • switchMap是用于缓存和状态更改的超重要运算符。 switchMap在内部运行该函数(在本例中为getData函数),并订阅其输出。这意味着switchMap的输出将是服务器数据的预订。
  • switchMap的另一个重要功能是取消先前的订阅。这意味着,如果您的服务器请求在另一个通知到达之前没有返回,它将取消旧的请求并再次ping服务器。

现在所有这些,服务器数据将通过.next(res)方法放置在缓存中。

现在所有最终组件所需要做的就是监听缓存以进行更新,并适当地进行处理:

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()
  }

}

实际的概念:

https://ngrx.io/

Angular App on button click