0
votes

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;
        }
      }
    });
  }
1
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
The form array is being generated using json data from a serverJake12342134
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.Harun Yilmaz
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
@sagat Unfortunately in this situation afterViewInit() will not work as the async calls occur after the call to afterViewInitJake12342134

1 Answers

0
votes

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