Search code examples
angulartypescriptrxjsngrxngxs

NGXS Store Custom Logic with RXJS before Rendering


I am currently trying to display a variable from my NGXS store dynamically in the frontend. This is usually no problem using the async pipe, but when I try to add logic the application freezes.

I have the variable:

printStyles$: Observable<PaperFormatDto[]> = this.store.select(PrintStyleState.getPrintStyles);

Now I want to dynamically transform this variable to make it a grouped array:

public getPrintStyleGroups(): Observable<any> {
    return from(this.printStyles$).pipe(
        filter(x => x != undefined),
        first(),
        mergeMap(x => x),
        groupBy(x => x.group),
        mergeMap(group => zip(of(group.key), group.pipe(toArray()))),
        toArray()
    );
  }

To display the variable now in the frontend in a grouped select:

<nb-select fullWidth id="paperFormatInput" name="paperFormat" placeholder="Paper Format">
    <nb-option-group *ngFor="let group of (getPrintStyleGroups() | async)" [title]="'test'">
        <nb-option [value]="0">Test</nb-option>
    </nb-option-group>
</nb-select>

Unfortunately, the attempt results in the Angular rendering logic calling the getPrintStyleGroups() function in an infinite loop, thus freezing the UI. What would be the best approach for my use case to dynamically display the transformed content of an NGXS store?

So far I have only tried to change the RXJS statement a little bit, unfortunately without success so far.


Solution

  • You shouldn't bind to functions in an Angular template as that function is called each time change detection is triggered and that especially holds true if that function returns an observable. getPrintStyleGroups returns a new observable each time it is called. So when you have a binding like *ngFor="let group of (getPrintStyleGroups() | async)" each time change detection runs you are generating a new observable.

    Instead you should be creating an observable that is a property on the component that doesn't change with each change detection.

    printStyleGroups$ = from(this.printStyles$).pipe(
      filter(x => x != undefined),
      first(),
      mergeMap(x => x),
      groupBy(x => x.group),
      mergeMap(group => zip(of(group.key), group.pipe(toArray()))),
      toArray()
    );
    

    and bind to this observable in the template with *ngFor="let group of (printStyleGroups$ | async)"