Angular - RxJS:afterViewInit 和异步管道

Sci*_*ion 5 javascript rxjs typescript angular

我尝试在我的组件中执行以下操作 changeDetection: ChangeDetectionStrategy.OnPush,

@ViewChild('searchInput') input: ElementRef;

ngAfterViewInit() {
    this.searchText$ = fromEvent<any>(this.input.nativeElement, 'keyup')
      .pipe(
        map(event => event.target.value),
        startWith(''),
        debounceTime(300),
        distinctUntilChanged()
      );
}
Run Code Online (Sandbox Code Playgroud)

并在模板中

<div *ngIf="searchText$ | async as searchText;">
  results for "<b>{{searchText}}</b>"
</div>
Run Code Online (Sandbox Code Playgroud)

它不起作用,但是如果我删除OnPush,它会起作用。我不太确定为什么因为异步管道应该触发更改检测。

编辑

根据答案,我尝试用以下内容替换我所拥有的内容:

this.searchText$ = interval(1000);
Run Code Online (Sandbox Code Playgroud)

没有任何@Inputasync管道正在标记我的组件进行检查,它工作得很好。所以我不明白为什么我没有和fromEvent

oni*_*nik 5

默认情况下,每当 Angular 启动更改检测时,它都会一一检查所有组件,检查是否有更改,如果更改则更新其 DOM。当您将默认更改检测更改为 时会发生什么ChangeDetection.OnPush

Angular 改变了它的行为,并且只有两种方法来更新组件 DOM。

  • @Input 属性引用已更改

  • 手动调用markForCheck()

如果您执行其中一项,它将相应地更新 DOM。在您的情况下,您不使用第一个选项,因此您必须使用第二个选项并markForCheck()在任何地方调用 。但有一种情况,每当你使用异步管道时,它都会为你调用这个方法。

异步管道订阅 Observable 或 Promise 并返回其发出的最新值。当发出新值时,异步管道会标记要检查更改的组件。当组件被销毁时,异步管道会自动取消订阅以避免潜在的内存泄漏。

所以这里没有什么魔法,它markForCheck()在幕后调用。但如果是这样,为什么你的解决方案不起作用?为了回答这个问题,让我们深入研究 AsyncPipe 本身。如果我们检查源代码 AsyncPipes 转换函数看起来像这样

transform(obj: Observable<any>|Promise<any>|null|undefined): any {
    if (!this._obj) {
      if (obj) {
        this._subscribe(obj);
      }
      this._latestReturnedValue = this._latestValue;
      return this._latestValue;
    }
    ....// some extra code here not interesting
 }
Run Code Online (Sandbox Code Playgroud)

因此,如果传递的值不是未定义的,它将订阅该可观察对象并相应地执行操作(markForCheck()每当值发出时调用)

现在这是最关键的部分 ,Angular 第一次调用转换方法时,它是未定义的,因为您在回调searchText$ngAfterViewInit()初始化(视图已经渲染,因此它也调用异步管道)。因此,当您初始化searchText$字段时,该组件的更改检测已经完成,因此它不知道searchText$已经定义了该字段,随后它不再调用 AsyncPipe,因此问题是它永远无法订阅 AsyncPipe变化,你要做的就是markForCheck()在初始化后只调用一次,Angular 在该组件上再次运行changeDetection,更新 DOM 并调用 AsyncPipe,它将订阅该可观察的

ngAfterViewInit() {
    this.searchText$ =
     fromEvent<any>(this.input.nativeElement, "keyup").pipe(
      map((event) => event.target.value),
      startWith(""),
      debounceTime(300),
      distinctUntilChanged()
    );
    this.cf.markForCheck();
  }
Run Code Online (Sandbox Code Playgroud)