最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Async pipe subscription not work correctly when nested inside ngIf - Stack Overflow

programmeradmin4浏览0评论

I have a quick search box which I would like to have a loading animation. I used ng-template with ngIf to show/hide this animation. And I have some li's nested inside the same div which subscribed to a search result Observable using async pipe to display results. This async pipe works great when there's no *ngIf on the parent div, but seems it is not subscribing anymore when I apply ngIf. Is this an expected behavior? Or am I doing anything wrong?

My markup looks like this.

<input #searchBox type="text" (keyup)="itemLoading=true;searchTerm$.next(searchBox.value)"/>
<div *ngIf="!itemLoading else loading">
<!--Remove ngIf then items will display correctly when search-->
<!-- <div> -->
  <ul>
    <li *ngFor="let item of result$ | async ">{{item}}</li>
  </ul>
</div>
<ng-template #loading>
  <p>LOADING...</p>
</ng-template>

And I am using switchMap to run the search:

private source = of(['apple', 'pear', 'banana']).pipe(delay(500));

  searchTerm$ = new Subject<string>();
  result$: Observable<string[]>;
  itemLoading = false;  

  constructor() {
    this.result$ = this.searchTerm$.pipe(
      tap(term => console.log('search term captured: ' + term)),
      debounceTime(300),
      distinctUntilChanged(),
      switchMap(() => this.source.pipe(
        tap(_ => {
            console.log('source reached');
            this.itemLoading = false;
          })
      ))
    );
  }

When ngIf is present in parent div, the 'source reached' message is never logged in console, as well as the loading template keeps hanging there.

Here is a full working example of what I am talking about:

I have a quick search box which I would like to have a loading animation. I used ng-template with ngIf to show/hide this animation. And I have some li's nested inside the same div which subscribed to a search result Observable using async pipe to display results. This async pipe works great when there's no *ngIf on the parent div, but seems it is not subscribing anymore when I apply ngIf. Is this an expected behavior? Or am I doing anything wrong?

My markup looks like this.

<input #searchBox type="text" (keyup)="itemLoading=true;searchTerm$.next(searchBox.value)"/>
<div *ngIf="!itemLoading else loading">
<!--Remove ngIf then items will display correctly when search-->
<!-- <div> -->
  <ul>
    <li *ngFor="let item of result$ | async ">{{item}}</li>
  </ul>
</div>
<ng-template #loading>
  <p>LOADING...</p>
</ng-template>

And I am using switchMap to run the search:

private source = of(['apple', 'pear', 'banana']).pipe(delay(500));

  searchTerm$ = new Subject<string>();
  result$: Observable<string[]>;
  itemLoading = false;  

  constructor() {
    this.result$ = this.searchTerm$.pipe(
      tap(term => console.log('search term captured: ' + term)),
      debounceTime(300),
      distinctUntilChanged(),
      switchMap(() => this.source.pipe(
        tap(_ => {
            console.log('source reached');
            this.itemLoading = false;
          })
      ))
    );
  }

When ngIf is present in parent div, the 'source reached' message is never logged in console, as well as the loading template keeps hanging there.

Here is a full working example of what I am talking about: https://stackblitz./edit/angular-2ukcqu

Share Improve this question edited Aug 22, 2018 at 21:45 Bahman Parsa Manesh 2,3703 gold badges18 silver badges35 bronze badges asked Aug 22, 2018 at 20:08 wctigerwctiger 1,0611 gold badge14 silver badges23 bronze badges 0
Add a ment  | 

3 Answers 3

Reset to default 11

Rewriting the *ngIf as hidden should solve the problem. The reason result$ isn't working is because those elements inside the *ngIf won't be added to the dom until itemLoading is false. At that point they'll subscribe to result$, but the event will have already occurred.

Alternatively shareReplay(1) might also do the trick without you having to rewrite anything else, as the reply will run when the Observable is subscribed too.

Solution A

<div [hidden]="itemLoading">
<!-- ... -->
</div>
<div [hidden]="!itemLoading">
  <p>LOADING...</p>
</div>

Solution B

this.result$ = this.searchTerm$.pipe(
  //...
  shareReplay(1)
);

Use async pipe within your *ngIf. You can find a great writeup at https://toddmotto./angular-ngif-async-pipe

In short:

<div *ngIf="(user$ | async) as user; else loading">
  <user-profile
    [user]="user">
  </user-profile>
  <user-messages
    [user]="user">
  </user-messages>
</div>

<ng-template #loading>
  Loading stuff...
</ng-template>

If this does not work for you, another cleaner solution (IMO) is to use BehaviorSubject. When your dom gets around to subscribing, it will get the last observable data.

If you do not want to add everything into your DOM, you can group your observables using forkJoin (RxJS doc) operator. Then you can keep your syntax with *ngIf and the DOM will stay clean.

Solution C

.ts

this.data$ = forkjoin([yourObs1, yourObs2,...]).pipe(
switchMap(myResolvedArray=>{
// Here you must return the object you want
return {
dataObs1: myResolvedArray[0]
...
}
})
)

.html

<div *ngIf='data$|async as data'>
//data.dataObs1 is accessible like other attributes
</div>
发布评论

评论列表(0)

  1. 暂无评论