Search code examples
angulartypescriptrxjsobservablengrx

In Angular how can I use ngrx to subscribe to multiple observables that are updated each time a dropdown value is changed


I have one dropdown list (dropdown a) bound to an observable collection and when it's value is changed I have to repopulate 2 other dropdowns (dropdown b and c) which are also bound to observable collections using the async pipe from 2 different stores.

The above part is easy and working well. I now want to add a loading spinner when you change dropdown a's value and b/c are being re-populated. I have the below method which is called when a is changed and it seems to be working but it doesn't feel right:

setDropDownLists(dropdownAValue: string): void {
  this.loadingDropdowns = true
  this.spinner.show(this.DropdownsLoadingSpinner);

  this.storeB.dispatch(new GetBItems(dropdownAValue));
  this.storeC.dispatch(new GetCItems(dropdownAValue));

  this.dropdownB = this.storeB.select(storeBSelectorByPermission(PermissionA));
  this.dropdownC = this.storeC.select(storeCSelector);

  combineLatest([this.dropdownB, this.dropdownC])
   .pipe(
     filter(([b, c]) => b!= null && c!= null),
     take(1),
   )
   .subscribe(() => {
     this.loadingDropdowns = false;
     this.spinner.hide(this.DropdownsLoadingSpinner);
  });
}

Something feels a bit off here and I feel like I've approach this all wrong. A few things to note:

  • I dispatch the actions as the store may not have loaded those relevant for dropdownAValue (there's a lot of values and I assumed not loading them all was a good approach).
  • The first selector takes in a parameter as I have to filter the store down (I used PermissionA as an example)
  • I have tried to make a global observable using combineLatest but [I think] because I overwrite the observable collections each time it loses it's subscription (which makes me wonder am I creating loads of subscription here every time the dropdown is changed.

Any help or pointers on how to do this "right" would be greatly appreciated.


Solution

  • cancelSpinner() {
                  if (this.loadingDropdowns === 1) {
                     this.spinner.hide();
                  } else {
                      this.loadingDropdowns -= 1;
                  }
    }
    
    setDropDownLists(dropdownAValue: string): void {
      this.loadingDropdowns = 2;
      this.spinner.show(this.DropdownsLoadingSpinner);
    
      this.storeB.dispatch(new GetBItems(dropdownAValue));
      this.storeC.dispatch(new GetCItems(dropdownAValue));
    
      this.dropdownB = this.storeB.pipe(
              select(storeBSelectorByPermission(PermissionA)),
               filter(v => filter for empty value to make this work on startup),
               tap( () => this.cancelSpinner())
    )
    
      this.dropdownC = this.storeC.pipe(
              select(storeCSelector),
              filter(v => ...),
              tap( () => this.cancelSpinner())
    )
    

    Something like this. The tap will run every time the selector emits. You probably want to put the selectors in the constructor, and in this function do the dispatches and spinner logic.