Angular - RxJS:afterViewInit 和异步管道 [英] Angular - RxJS : afterViewInit and Async pipe

查看:34
本文介绍了Angular - RxJS:afterViewInit 和异步管道的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

@ViewChild('searchInput') 输入:ElementRef;ngAfterViewInit() {this.searchText$ = fromEvent(this.input.nativeElement, 'keyup').管道(地图(事件 => 事件.目标.值),从...开始(''),去抖动时间(300),distinctUntilChanged());}

在模板中

<div *ngIf="searchText$ | async as searchText;">{{searchText}}"的结果

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

编辑:

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

this.searchText$ = interval(1000);

在没有任何 @Input 的情况下,async 管道将我的组件标记为检查,并且它工作得很好.所以我不明白为什么我没有与 fromEvent

相同的行为

解决方案

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

Angular 改变了它的行为,只有两种方法可以更新组件 DOM.

  • @Input 属性引用已更改

  • 手动调用 markForCheck()

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

<块引用>

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

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

transform(obj: Observable|Promise|null|undefined): any {如果(!this._obj){如果(对象){this._subscribe(obj);}this._latestReturnedValue = this._latestValue;返回 this._latestValue;}....//这里有一些额外的代码并不有趣}

因此,如果传递的值不是未定义的,它将订阅该 observable 并采取相应的行动(调用 markForCheck(),每当值发出时)

现在是最关键的部分Angular第一次调用transform方法是未定义的,因为你在ngAfterViewInit()回调里面初始化了searchText$(View已经渲染好了,所以它也调用了异步管道).所以当你初始化searchText$字段的时候,这个组件的变化检测已经完成了,所以它不知道searchText$已经被定义了,随后它也不知道再调用 AsyncPipe,所以问题是它永远不会让 AsyncPipe 订阅这些更改,您需要做的是在初始化后仅调用一次 markForCheck(),Angular 在该组件上再次运行 changeDetection, 更新 DOM 并调用 AsyncPipe,它将订阅该 observable

ngAfterViewInit() {this.searchText$ =fromEvent(this.input.nativeElement, "keyup").pipe(map((event) => event.target.value),从...开始(""),去抖动时间(300),distinctUntilChanged());this.cf.markForCheck();}

I tried to do the following in my component which uses 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()
      );
}

And in the template

<div *ngIf="searchText$ | async as searchText;">
  results for "<b>{{searchText}}</b>"
</div>

It doesn't work, however if I remove the OnPush, it does. I am not too sure why since the async pipe is supposed to trigger the change detection.

Edit:

Following the answers, I have tried to replace what I have by the following:

this.searchText$ = interval(1000);

Without any @Input, the async pipe is marking my component for check and it works just fine. So I don't get why I haven't got the same behavior with the fromEvent

解决方案

By default Whenever Angular kicks change detection, it goes through all components one by one and checks if something changes and updates its DOM if it's so. what happens when you change default change detection to ChangeDetection.OnPush?

Angular changes its behavior and there are only two ways to update component DOM.

  • @Input property reference changed

  • Manually called markForCheck()

If you do one of those, it will update DOM accordingly. in your case you don't use the first option, so you have to use the second one and call markForCheck(), anywhere. but there is one occasion, whenever you use async pipe, it will call this method for you.

The async pipe subscribes to an Observable or Promise and returns the latest value it has emitted. When a new value is emitted, the async pipe marks the component to be checked for changes. When the component gets destroyed, the async pipe unsubscribes automatically to avoid potential memory leaks.

so there is nothing magic here, it calls markForCheck() under the hood. but if it's so why doesn't your solution work? In order to answer this question let's dive in into the AsyncPipe itself. if we inspect the source code AsyncPipes transform function looks like this

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
 }

so if the value passed is not undefined, it will subscribe to that observable and act accordingly (call markForCheck(), whenever value emits)

Now it's the most crucial part the first time Angular calls the transform method, it is undefined, because you initialize searchText$ inside ngAfterViewInit() callback (the View is already rendered, so it calls async pipe also). So when you initialize searchText$ field, the change detection already finished for this component, so it doesn't know that searchText$ has been defined, and subsequently it doesn't call AsyncPipe anymore, so the problem is that it never get's to AsyncPipe to subscribe on those changes, what you have to do is call markForCheck() only once after the initialization, Angular ran changeDetection again on that component, update the DOM and call AsyncPipe, which will subscribe to that observable

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

这篇关于Angular - RxJS:afterViewInit 和异步管道的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆