0

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);
  }
}
Rohan Fating
  • 2,135
  • 15
  • 24
joakimk
  • 822
  • 9
  • 26
  • Maybe you should use Observable.forkJoin, it waits untill all observables complete and emit the last value from each. – Anton Lee Sep 06 '17 at 08:37
  • Obviously, in the simple solution, the component won't know *which* parameter actually changed but in my (specific) case I'll be invoking the API (refreshing data) using all parameters every time one or more changes. So, it should work for my scenario but maybe the approach is too lax. – joakimk Sep 06 '17 at 08:38
  • I guess you can add the if clause `if(this.param2){this.getDataFromAPI(this.param1, this.param2); }` to the first subscription and `if(this.param1){this.getDataFromAPI(this.param1, this.param2); }` to the second one. this will insure that `getDataFromApi` will be executed only if both params are defined. OfC everytime one of them changes it will execute the update needed – marouane kadiri Sep 06 '17 at 08:55
  • Yes, that's sort of what I'm describing above :) What I'm looking for is a "better solution", perhaps using promises or await. – joakimk Sep 06 '17 at 09:01

1 Answers1

1

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

Heehaaw
  • 2,677
  • 17
  • 27