*ngIf with multiple async pipe variables

Angular

Angular Problem Overview


Been trying to combine two observables into one *ngIf and show the user interface when both have emitted.

Take:

<div *ngIf="{ language: language$ | async, user: user$ | async } as userLanguage">
    <b>{{userLanguage.language}}</b> and <b>{{userLanguage.user}}</b>
</div>

From: https://stackoverflow.com/questions/44855599/putting-two-async-subscriptions-in-one-angular-ngif-statement

This works as far as it compiles however in my case language$ and user$ would be from two HTTP requests and it seems user$ throws runtime errors like TypeError: _v.context.ngIf.user is undefined.

Essentially what I really want is (this doesn't work):

<div *ngIf="language$ | async as language && user$ | async as user">
    <b>{{language}}</b> and <b>{{user}}</b>
</div>

Is the best solution:

  • Subscribe inside the component and write to variables
  • To combine the two observables inside the component with say withLatestFrom
  • Add null checks {{userLanguage?.user}}

Angular Solutions


Solution 1 - Angular

This condition should be handled with nested ngIf directives:

<ng-container *ngIf="language$ | async as language">
  <div *ngIf="user$ | async as user">
    <b>{{language}}</b> and <b>{{user}}</b>
  </div>
<ng-container>

The downside is that HTTP requests will be performed in series.

In order to perform them concurrently and still have language and user variables, more nesting is required:

<ng-container *ngIf="{ language: language$ | async, user: user$ | async } as userLanguage">
  <ng-container *ngIf="userLanguage.language as language">
    <ng-container *ngIf="userLanguage.user as user">
      <div><b>{{language}}</b> and <b>{{user}}</b></div>
    </ng-container>
  </ng-container>
</ng-container>

More efficient way way to do this is to move logic from template to component class at this point and create a single observable, e.g. with withLatestFrom

Solution 2 - Angular

You can also use the following trick. You will need one additional nesting.

<ng-container *ngIf="{a: stream1$ | async, b: stream2$ | async, c: stream3$ | async} as o">
  <ng-container *ngIf="o.a && o.b && o.c">
    {{o.a}} {{o.b}} {{o.c}}
  </ng-container>
</ng-container>

The object o is ever truthy, therefore the first *ngIf is simple used to save the stream values. inside you have to namespace your variables with o.

Solution 3 - Angular

That's depend what do you want but I think forkJoin operator with a loaded flag, could be a good idea.

https://www.learnrxjs.io/operators/combination/forkjoin.html

The forkJoin wait that all Observable are completed to return their values in its subscribe

Observable.forkJoin(
  Observable.of("my language").delay(1000),
  Observable.of("my user").delay(1000),
).subscribe(results => {
  this.language = results[0]
  this.user = results[1]
})

You can catch errors into onError of the subscribe and display it.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionPhilView Question on Stackoverflow
Solution 1 - AngularEstus FlaskView Answer on Stackoverflow
Solution 2 - AngularwestorView Answer on Stackoverflow
Solution 3 - AngularmickaelwView Answer on Stackoverflow