0

I'm struggling with understanding how to merge two observables and make use of their merged product. I've watched countless videos on mergeMap, switchMap, flatMap, marble diagrams etc but I still don't get how merging observables works. I feel like I'm not going to be efficient, or even correct, when using RxJS.

I have an observable that I'm subscribing to, and I want to also subscribe to the valueChanges observable of a particular form array within my code. However I need to ensure that the second subscription only occurs after the form array has been properly built otherwise I'll get null errors.

Obviously one was to do this is to subscribe to valueChanges within the next function of my first subscription, however this is bad practice and I want to avoid it. However I'm not sure in what way I should be structuring my code so that I get the behaviour I want without using nested subscriptions.

setSettings$(serial: string) {
    return this.getSettingsFromSerial$(serial).pipe(
      tap(val => {
        this.settingsState.savedSettingsState = val;
        this.settingsState.ipRestrictionEnabled = val.ipRestrictionSettings.ipRestrictionEnabled;
        if(val.ipRestrictionSettings.ipRanges.length === 0){
          this.addEmptyRange();
        }
        else
        {
          for (const i of val.ipRestrictionSettings.ipRanges) {
            this.addRange(i.startRange, i.endRange, i.label);
          }
        }
        this.settingsState.displaySettings = true;
        this.settingsState.displayForm = true;
        this.hideRangeErrorsUntilNotPristine(); <-- I need to merge (?) this with my first observable to ensure that it happens after the form is built.
      })
    );
  }

  // TODO :: Cancel this on destroy 
  hideRangeErrorsUntilNotPristine(){
    this.ipRangeFormArray.valueChanges.subscribe( res => {
      let formGroups = this.ipRangeFormArray.controls;

      for(let i = 0; i < formGroups.length; i++){
        if(formGroups[i].pristine === true) {
          this.settingsState.ipRangeValidStates[i].displayError = false;
        }
        else {
          this.settingsState.ipRangeValidStates[i].displayError = true;
        }
      }
    });
  }
9
  • Is ur formarray getting json data from a server and needs that to get created or do u simply generate the formarray from json config data present in ur project?
    – sagat
    Commented Aug 28, 2019 at 11:28
  • The form array is being generated using json data from a server Commented Aug 28, 2019 at 11:28
  • 1
    If you want to wait for one observable to emit value to fire the second observable, you can use concatMap. It ensures that the second one will be called after the first one emits a value. You can have a look at this interactive marble diagrams to have a better understanding. Commented Aug 28, 2019 at 11:32
  • As this is one suggestion. I am not sure if the form always been created, when he tries to subscribe to valueChanges, as this would lead to undefined. Usually I would use afterViewInit() callback.
    – sagat
    Commented Aug 28, 2019 at 11:35
  • @sagat Unfortunately in this situation afterViewInit() will not work as the async calls occur after the call to afterViewInit Commented Aug 28, 2019 at 12:17

1 Answer 1

0

From what I understand, all you need to do is make sure that this method is called once the FormControl objects have been instantiated in your TypeScript code. I've chosen mergeMap for no particular reason, as you only need to worry about which operator to use if the outer Observable emits multiple times.

setSettings$(serial: string) {
    return this.getSettingsFromSerial$(serial).pipe(
      tap(val => {
        this.settingsState.savedSettingsState = val;
        this.settingsState.ipRestrictionEnabled = val.ipRestrictionSettings.ipRestrictionEnabled;
        if(val.ipRestrictionSettings.ipRanges.length === 0){
          this.addEmptyRange();
        }
        else
        {
          for (const i of val.ipRestrictionSettings.ipRanges) {
            this.addRange(i.startRange, i.endRange, i.label);
          }
        }
        this.settingsState.displaySettings = true;
        this.settingsState.displayForm = true;
      }),
      // this gets called once everything in the `tap` has finished,
      // because everything is synchronous
      mergeMap(() => this.hideRangeErrorsUntilNotPristine())
    );
  }

Not the answer you're looking for? Browse other questions tagged or ask your own question.