93

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: 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}}

3 Answers 3

151

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

Sign up to request clarification or add additional context in comments.

4 Comments

The last sentence in this answer is the answer! Create a single observable in the component class instead of nesting!
If you're only going to use the "combined observable" in the template anyway, why write it out longhand in the class, when the object syntax is easy to read and understand, and lives in the same place where it's being used? I would just get rid of the two inner ng-containers, and refer to obj.user / obj.language directly in the templates.
@Coderer The OP has the requirement to use values as <b>{{language}}</b> and <b>{{user}}</b>. ngIf is the way language, etc temporary variables can be assigned in a template without the use of third-party directives. In case this is unnecessary, nested ng-containers can be omitted.
Thanks for this! creating an object of piped async members is what I was looking to do :)
47

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.

5 Comments

Excellent, haven't seen the outter ngIf object approach before
I like that this is an option, but really, this is the same as myStreams$=combineLatest([stream$1,stream2$,stream3$], (a, b, c,) => ({a, b, c})).pipe( filter((c) => Object.values(c).every((v) => v != null)) ); and then <ng-container *ngIf="myStreams$ | async; let o;">...</ng-container>
@chrismarx, could filter((c) => Object.values(c).every((v) => v != null)) be extracted as a function reference like this: pipe(filterNonNull) ... and re-used? I tried it but got confused how to make it generic.
@andy yes, maybe review how rxjs pipes work - stackoverflow.com/a/69377067/228369
That's one hell of an ugly abuse of the language. I wouldn't do that. Not to mention that the code doesn't explain itself.
9

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.

2 Comments

It should be noticed that forkJoin will work only on completed observables (which Http observables are).
@estus Yes, that's why I wrote that's depend what do you want :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.