multitaskPro
multitaskPro

Reputation: 571

Async pipe within ngIf directive

When using the AsyncPipe inside of an *ngIf, if the Observable connected to the AsyncPipe pushes its values before the *ngIf becomes true, the value returned from the AsyncPipe will be incorrect.

For example, let's say I have:

<div *ngIf="showPipe">
    <div *ngFor="let item of arrObs | async">{{item}}</div>
</div>

Then say events happen in this order:

  1. showPipe is false
  2. arrObs pushes [1,2,3]
  3. showPipe is set to true

From what I've seen, the *ngFor will act as if arrObs | async returned null.

One solution to this problem is to use [hidden] instead, but there are a lot of benefits to *ngIf, like performance and making null handling easier.

What's the proper way to do this? Should I just not use an observable at all for displaying content? I had assumed that using an observable was the most Angular-y way of doing things.

Edit: My observable is actually just a new Subject() which I call next() on.

Upvotes: 3

Views: 2982

Answers (3)

Shaya
Shaya

Reputation: 2932

In NgRx 10 and above, you can use the *ngrxLet directive along with the @ngrx/component package (documentation).

Usage example:

<ng-container *ngrxLet="observableNumber$ as n">
    <app-number [number]="n">
    </app-number>
</ng-container>

Upvotes: 0

Poul Kruijt
Poul Kruijt

Reputation: 71891

There are some ways you can solve this. I would suggest adding a shareReplay operator on your Observable:

readonly arrObs = this.someDataFromSomewhere().pipe(
  shareReplay(1)
);

if your Observable is actually a Subject, you can also change it to a BehaviorSubject or ReplaySubject

This way you will always receive the most up to date data on subscription.

There are also ways to handle this in the template and still maintain the *ngIf benefits:

<ng-container *ngIf="arrObs | async as arr">
  <div *ngIf="showPipe">
    <div *ngFor="let item of arr">{{item}}</div>
  </div>
</ng-container>

Or if you don't want the *ngIf to wait for the observable

<ng-container *ngIf="{ arr: arrObs | async } as data">
 <div *ngIf="showPipe">
   <div *ngFor="let item of data.arr">{{item}}</div>
 </div>
</ng-container>

Upvotes: 4

Șold Andrei-Alexandru
Șold Andrei-Alexandru

Reputation: 156

Surround your structure in an ng-container which contains the async pipe and stores the value in a variable using as. Also use the ng-if directive to load the container only after you get a value in that async pipe.

<ng-container *ngIf="( arrObs| async ) as array">
    <div *ngIf="showPipe">
        <div *ngFor="let item of array">{{item}}</div>
    </div>
</ng-container>

Upvotes: 2

Related Questions