'How to handle ngrx let with many subscribes as ng-container inside ng-container?

I start using ngrx let to using plain objects.

The problem is when I use a lot of observables in my component, I end up with the code which is seems bad (ng-container inside ng-container inside ng-container inside ng-container ...):

<ng-container *ngrxLet="obs1$ as obs1">
 <ng-container *ngrxLet="obs2$ as obs2">
  <ng-container *ngrxLet="obs3$ as obs3">
   <ng-container *ngrxLet="obs4$ as obs4">

    <app-comp [data]="obs1" ..>..</app-comp>
    <app-comp2 [data]="obs1" [data2]="obs3"..>..</app-comp2>
    <app-comp3 [data]="obs1" ..>..</app-comp3>
    <app-comp4 [data]="obs1" ..>..</app-comp4>
    <app-comp5 [data]="obs4" [data11]="obs1" ..>..</app-comp5>
    <app-comp6 [data]="obs2" ..>..</app-comp6>
    <app-comp7 [data]="obs3" ..>..</app-comp7>
  </app-number>
</ng-container>

Is there a better way to handle this syntax?



Solution 1:[1]

You could combine the observables in the controller using RxJS functions like combineLatest, zip and forkJoin. Each has a specific mechanism and you could find the differences between them here.

Illustration using combineLatest

Controller

combined$: Observable<any>;

ngOnInit() {
  this.combined$ = combineLatest(
    obs1$.pipe(startWith(null)),
    obs2$.pipe(startWith(null)),
    obs3$.pipe(startWith(null)),
    obs4$.pipe(startWith(null))
  ).map(([obs1, obs2, obs3, obs4]) => ({  // <-- `map` emits a user-friendly object
    obs1: obs1,
    obs2: obs2,
    obs3: obs3,
    obs4: obs4
  }));
}

Template

<ng-container *ngrxLet="combined$ as data">
  <app-comp [data]="data?.obs1" ..>..</app-comp>
  <app-comp2 [data]="data?.obs1" [data2]="data?.obs3"..>..</app-comp2>
  <app-comp3 [data]="data?.obs1" ..>..</app-comp3>
  <app-comp4 [data]="data?.obs1" ..>..</app-comp4>
  <app-comp5 [data]="data?.obs4" [data11]="data?.obs1" ..>..</app-comp5>
  <app-comp6 [data]="data?.obs2" ..>..</app-comp6>
  <app-comp7 [data]="data?.obs3" ..>..</app-comp7>
</ng-container>

However there is a caveat. combineLatest and zip won't start emitting unless each of the source observable has emitted at least once. We can enforce it using the startWith operator like shown here. But then you need to make sure the first value (null here) doesn't affect the data binding. You could of course replace *ngrxLet with *ngIf to avoid emitting the null, but then you'd lose the benefits of *ngrxLet.

Solution 2:[2]

ngrxLet does not seem to support that feature, you could use combineLatest as mentioned in previous answer or try some trcik i used with *ngIf

<ng-container *ngIf="{
  obs1: obs1$ | async,
  obs2: obs2$ | async,
  obs3: obs3$ | async,
  obs4: obs4$ | async
} as data">
    <app-comp [data]="data.obs1"></app-comp>
    <app-comp2 [data]="data.obs1" [data2]="data.obs3"></app-comp2>
    <app-comp3 [data]="data.obs1"></app-comp3>
    <app-comp4 [data]="data.obs1"></app-comp4>
    <app-comp5 [data]="data.obs4" [data11]="data.obs1"></app-comp5>
    <app-comp6 [data]="data.obs2"></app-comp6>
    <app-comp7 [data]="data.obs3"></app-comp7>
</ng-container>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 ruth
Solution 2