嵌套在 ngIf 中时,异步管道订阅无法正常工作

Posted

技术标签:

【中文标题】嵌套在 ngIf 中时,异步管道订阅无法正常工作【英文标题】:Async pipe subscription not work correctly when nested inside ngIf 【发布时间】:2019-01-29 03:48:00 【问题描述】:

我有一个快速搜索框,我想要一个加载动画。我使用ng-templatengIf 来显示/隐藏这个动画。我有一些 li 嵌套在同一个 div 中,该 div 使用异步管道订阅搜索结果 Observable 来显示结果。当父 div 上没有 *ngIf 时,此异步管道效果很好,但当我应用 ngIf 时,它似乎不再订阅。这是预期的行为吗?还是我做错了什么?

我的标记看起来像这样。

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

我正在使用 switchMap 来运行搜索:

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;
          )
      ))
    );
  

ngIf 出现在父 div 中时,“到达源”消息永远不会登录到控制台,并且加载模板一直挂在那里。

这是我正在谈论的完整工作示例:https://stackblitz.com/edit/angular-2ukcqu

【问题讨论】:

【参考方案1】:

如果您不想将所有内容添加到 DOM 中,您可以使用 forkJoin (RxJS doc) 运算符对您的 observables 进行分组。然后你可以使用*ngIf 保持你的语法并且DOM 将保持干净。

解决方案 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>

【讨论】:

【参考方案2】:

*ngIf 中使用async 管道。你可以在https://toddmotto.com/angular-ngif-async-pipe找到一篇很棒的文章

简而言之:

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

如果这对您不起作用,请使用另一种更清洁的解决方案 (IMO) BehaviorSubject。当您的 dom 开始订阅时,它将获得最后可观察到的数据。

【讨论】:

【参考方案3】:

*ngIf 重写为隐藏应该可以解决问题。 result$ 不起作用的原因是因为 *ngIf 中的那些元素在 itemLoading 为 false 之前不会被添加到 dom 中。那时他们将订阅 result$,但该事件已经发生了。

另外,shareReplay(1) 也可以做到这一点,而无需您重写任何其他内容,因为订阅 Observable 时也会运行回复。

解决方案 A

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

解决方案 B

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

【讨论】:

shareReplay 确实解决了这个问题。谢谢!

以上是关于嵌套在 ngIf 中时,异步管道订阅无法正常工作的主要内容,如果未能解决你的问题,请参考以下文章

将两个异步订阅放在一个 Angular *ngIf 语句中

如何使用来自html的异步管道获取可观察对象的嵌套值

如何使用异步管道值处理`ngIf`?

如何在Angular 6中将异步管道的结果分配给没有*ngIf的变量

使用“* ngIf”的Component构造函数中的BehaviourSubject订阅会抛出“ExpressionChangedAfterItHasBeenCheckedError”

Angular 5:无法使用异步管道更新模板