65

There's quite good doc of using *ngIf in Angular: https://angular.io/api/common/NgIf But, is that possible to have *ngIf async variable and multiple checks on that? Something like:

<div *ngIf="users$ | async as users && users.length > 1">
...
</div>

Of course, it's possible to use nested *ngIf, like:

<div *ngIf="users$ | async as users">
    <ng-container *ngIf="users.length > 1">
    ...
    </ng-container>
</div>

but it'd be really nice to use only one container, not two.

7 Answers 7

45

You can do this:

<ng-template ngFor let-user [ngForOf]="users$ | async" *ngIf="(users$ | async)?.length > 1 && (users$ | async)?.length < 5">
  <div>{{ user | json }}</div>
</ng-template>

Keep in mind that when using a subscription from a http-request, this would trigger the request twice. So you should use some state-management pattern or library, to not make that happen.

Here is a stackblitz.

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

17 Comments

yep, that's good suggestion, but what about more complex conditions: <div *ngIf="users$ | async as users && users.length > 1 && users.length < 5"> <div *ngFor="let user of users">...
what kind of ? you can have multiple statements like this. in one *ngIf, just chain em.
in general, i want to keep local variable for further usage with only one subscription and your solution has no variable and multiple subsriptions
Has anyone actually tested this? (I haven't but) In my understanding of Angular, you cannot use multiple structural directives on the same element. So *ngIf and *ngFor normally cannot be used together on the same element!
this solution will cause to fetch data from the server several time, due to using async multiple times.
|
34

I see everyone using *ngFor and *ngIf together in one tag and similar work-arounds, but I think that is an anti-pattern. In most regular coding languages, you don't do an if statement and a for loop on the same line do you? IMHO if you have to work around because they specifically don't want you to, you're not supposed to do that. Don't practice what's not "best practice."

✅✅✅ Keep it simple, if you don't need to declare the $implicit value from users$ | async as users:

<!-- readability is your friend -->
<div *ngIf="(users$ | async).length > 1"> ... </div>

But if you do need to declare as user, wrap it.


✅ For Complex Use Case; mind you, the generated markup will not even have an HTML tag, that's the beauty of ng-templates & ng-containers!

@alsami's accepted, edited answer works because *ngFor with asterisk is a shorthand for ng-template with ngFor (no asterisk), not to mention the double async pipe 🙏. The code really smells of inconsistency when you combine a no-asterisk ngFor with a *ngIf; you get the gist. The *ngIf takes precedence so why not just wrap it in a ng-container/ng-template? It won't wrap your inner elements using an HTML tag in the generated markup.

<div *ngIf="users$ | async as prods; then thenB; else elseB"></div>

<ng-template #thenB>
  <!-- inner logic -->
  <div *ngIf="prods?.length > 1 && users?.length < 5; else noMatchB">
    Loaded and inbound
  </div>
  <ng-template #noMatchB>Loaded and out of bound</ng-template>
</ng-template>

<ng-template #elseB>Content to render when condition is false.</ng-template>

❌ Don't do this

<!-- Big NO NO -->
<div *ngIf="(users$ | async)?.length > 1 && (users$ | async)?.length < 5"> ... </div>

Pipes are a bit daunting after you know about their sensitivity to life cycles; they update like mad. That's why Angular do NOT have SortPipe nor FilterPipe. So, erm, don't do this; you're basically creating 2 Observables that sometimes update frantically and may cause long-term data mismatch in its children if I'm correct.

3 Comments

what if the use case is like <ng-conatiner *ngIf="someValue && othervalue | asyn !==null"> //this is done to avoid false and 0 <ng-conatiner *ngIf=" othervalue | asyn >1"> </ng-conatiner> </ng-conatiner>
@Darpan For your case. You don't need to say othervalue | async !== null in the first line. The reason is <ng-conatiner *ngIf=" othervalue | asyn >1"> </ng-conatiner> already checks for null & checks if it's larger than 1 due to the weird nature of JS; why do it twice to complicate it?
The complex use case example gives me "TS2339: Property 'prods' does not exist on type 'AppComponent'." on Angular 16.2
23

I hit the same issue of needing an *ngIf + async variable with multiple checks.

This ended up working well for me.

<div *ngIf="(users$ | async)?.length > 0 && (users$ | async) as users"> ... </div>

or if you prefer

<div *ngIf="(users$ | async)?.length > 0 && (users$ | async); let users"> ... </div>

Explanation

Since the result of the if expression is assigned to the local variable you specify, simply ending your check with ... && (users$ | async) as users allows you to specify multiple conditions and specify what value you want the local variable to hold when all your conditions succeed.

Note

I was initially worried that using multiple async pipes in the same expression may create multiple subscriptions, but after some light testing (I could be wrong) it seems like only one subscription is actually made.

1 Comment

I did this, and it creates two subscriptions
9

Here's the alternative version, with a little bit cleaner template:

<ng-template [ngIf]="(users$ | async)?.length > 1" [ngIfElse]="noUsersMessage">
  <div *ngFor="let user of (users$ | async)">{{ user | json }}</div>
</ng-template>

<ng-template #noUsersMessage>No users found</ng-template>

Note that we use users$ | async 2 times. That will work if you add shareReplay() operator to user$ Observable:

  public users$: Observable<any[]> = this.someService.getUsers()
    .pipe(shareReplay());

That way, inner template will be able to access last value of the Observable and display the results.

You can try it out on stackblitz.

4 Comments

The use of share replay is a better approach.
This should get upvoted. Multiple use of async pipe on the same observables will get multiple network call to the same resource if we inspect. In most common scenarios, shareReplay should be used to reduce that down to 1 call instead.
This will show noUsersMessage while users$ is not yet resolved and cause flicker.
@Vedran yes indeed, to avoid that one can use separate variable (i.e. loading$) to track loading state. And when it resolves to truthy value, subscribe to users$ in inner template
4
<div class="mb-5 mt-5 mat-typography text-center">
    <div *ngIf="(applications$ | async) as applications; else noApplication">
      <div *ngIf="applications != null && applications.length > 0; else noApplication">
        show all job applications
      </div>
    </div>
  <ng-template #noApplication>
    <h3>You haven't applied to any jobs. </h3>
  </ng-template>
</div>

2 Comments

Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.
really good case. Exactly that I was looking for
2

Since Angular 18, you can use the @let syntax and simply write

@let users = users$ | async;
<div *ngIf="users?.length > 1">
...
</div>

Comments

0
<div *ngIf="(users$ | async)?.length" >
    .....
    .....
    .....
    .....
</div>

Comments

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.