Angular: Invoke method when multiple subscriptions (BehaviorSubjects) are all defined

1.2k views Asked by At

I've set up a "global" service, to share parameters across components in my Angular app:

import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';

@Injectable()
export class ParameterService {

  param1 = new BehaviorSubject(null);
  publishParam1(data: number) {
    this.param1.next(data);
  }

  param2 = new BehaviorSubject(null);
  publishParam2(data: number) {
    this.param2.next(data);
  }

}

Components which need either or both of these parameters can subscribe, and be notified when these change:

  private subscriptions: Array<Subscription> = [];
  param1: number; // keep local copies of the params
  param2: number;

  ngOnInit(): void {

    this.subscriptions.push(this._parameterService.param1.subscribe(
      data => {
          console.log("param1 changed: " + data);
          if (data != null) {
            this.param1 = data;
            // NB! this.param2 may be undefined!
            this.getDataFromAPI(this.param1, this.param2);
        }
      }
    ));

    this.subscriptions.push(this._parameterService.param2.subscribe(
      data => {
          console.log("param2 changed: " + data);
          if (data != null) {
            this.param2 = data;
            // NB! this.param1 may be undefined!
            this.getDataFromAPI(this.param1, this.param2);
        }
      }
    ));
  }

  ngOnDestroy() {
    // https://stackoverflow.com/questions/34442693/how-to-cancel-a-subscription-in-angular2
    this.subscriptions.forEach((subscription: Subscription) => {
            subscription.unsubscribe();
        });
  }

param1 and param2 are initialized by AppComponent, asynchronously, so the components subscribing to both parameters will be "notified" (receive the values) in arbitrary order. getDataFromAPI should get new data whenever either parameter changes, so both subscriptions call the method, but the other parameter may still be undefined.

Obviously, this can easily be fixed by simply checking if the other parameter is defined before invoking getDataFromAPI, but I'm wondering what's the best way to handle this (surely common) scenario? Should I use promises or await to ensure that getDataFromAPI is only invoked when both (all) parameters are defined?

One simple idea which came to mind is to ensure the ParameterService only contains a single parameter; i.e. wrap param1 and param2 in some "state class":

export class StateObject {
    param1: number;
    param2: number;

    constructor() { }
}

such that ParameterService becomes,

import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { StateObject } from './stateobject';

@Injectable()
export class ParameterService {

  state = new BehaviorSubject(null);
  publishState(data: StateObject) {
    this.state.next(data);
  }
}
1

There are 1 answers

0
Heehaaw On BEST ANSWER

You could try:

let subscription  = Rx.Observable.combineLatest(
    this._parameterService.param1,
    this._parameterService.param2
  )
  .subscribe(data => {
      console.log("param1 or param2 changed: " + data);
      let [p1, p2] = data;
      this.getDataFromAPI(this.param1 = p1, this.param2 = p1);
    })
);

Or with projection function:

let subscription  = Rx.Observable.combineLatest(
    this._parameterService.param1,
    this._parameterService.param2,
    (p1, p2) => { p1: p1, p2: p2 }
  )
  .subscribe(data => {
      console.log("param1 or param2 changed: " + data);
      this.getDataFromAPI(this.param1 = data.p1, this.param2 = data.p2);
    })
);

combineLatest API reference