javascript - Angular - RxJS : afterViewInit and Async pipe - Stack Overflow

I tried to do the following in my ponent which uses changeDetection: ChangeDetectionStrategy.OnPush,@

I tried to do the following in my ponent 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 ponent for check and it works just fine. So I don't get why I haven't got the same behavior with the fromEvent

I tried to do the following in my ponent 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 ponent for check and it works just fine. So I don't get why I haven't got the same behavior with the fromEvent

Share Improve this question edited Jun 18, 2020 at 13:22 Scipion asked Jun 18, 2020 at 6:49 ScipionScipion 11.9k23 gold badges81 silver badges154 bronze badges
Add a ment  | 

3 Answers 3

Reset to default 5

By default Whenever Angular kicks change detection, it goes through all ponents 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 ponent 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 ponent to be checked for changes. When the ponent 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 ponent, 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 ponent, 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();
  }

The changeDetection: ChangeDetectionStrategy.OnPush allow to the ponent to not triggered the changeDetection all the time but just when an @Input() reference is updated. So if you do all your stuff in the same ponent, no @Input() reference are updated so the view is not updated.

I propose you to Create your dumb ponent with your template code above, but give it the searchText via an @Input(), and call your dumb ponent in your smart ponent

Smart ponent

<my-dumb-ponent [searchText]="searchText$ | async"></my-dumb-ponent>

Dumb ponent

@Input() searchText: SearchText

template

<div *ngIf="searchText">
  results for "<b>{{searchText}}</b>"
</div>

This is because Angular is updates DOM interpolations before ngAfterViewInit and ngAfterViewChecked. I know this sounds confusing a bit. It's because of the first change detection cycle Angular does. Referring to Max Koretskyi's article about change detection algorithm of Angular, in a change detection cycle these happens sequentially:

  1. sets ViewState.firstCheck to true if a view is checked for the first time and to false if it was already checked before
  2. checks and updates input properties on a child ponent/directive instance
  3. updates child view change detection state (part of change detection strategy implementation)
  4. runs change detection for the embedded views (repeats the steps in the list)
  5. calls OnChanges lifecycle hook on a child ponent if bindings changed
  6. calls OnInit and ngDoCheck on a child ponent (OnInit is called only during first check)
  7. updates ContentChildren query list on a child view ponent instance
  8. calls AfterContentInit and AfterContentChecked lifecycle hooks on child ponent instance (AfterContentInit is called only during first check)
  9. updates DOM interpolations for the current view if properties on current view ponent instance changed
  10. runs change detection for a child view (repeats the steps in this list)
  11. updates ViewChildren query list on the current view ponent instance
  12. calls AfterViewInit and AfterViewChecked lifecycle hooks on child ponent instance (AfterViewInit is called only during first check)
  13. disables checks for the current view (part of change detection strategy implementation)

As you see, Angular updates DOM interpolations (at step 9) after AfterContentInit and AfterContentChecked hooks are called, so if you call rxjs subscriptions in AfterContentInit or AfterContentChecked lifecycle hooks (or earlier, like OnInit etc.) your DOM will be updated because Angular updates DOM at step 10, and when you change something in ngAfterViewInit() and you are using OnPush, Angular won't update DOM because you are at step 12 on ngAfterViewInit() and Angular has already updated DOM before you change something!

There are workaround solutions to avoid this to subscribe it in ngAfterViewInit. First, you can call markForCheck() function, so you basically say by using it on the first cycle that "hey Angular, you updated DOM on step 9, but I have something to change at step 12, so please be careful, have a look at ngAfterViewInit I have still something to change". Or as a second solution, you can trigger a change detection manually again (by triggering and event handler or using detecthanges() function of ChangeDetectorRef) so that Angular repeats all these steps again, and when it reaches at step 9 again, Angular updates your DOM.

I have created a Stackblitz example that you can try these out. You can unment the lines of subscriptions placed in lifecycle hooks 1 by 1, so that you can see after which lifecycle hook Angular updates DOM. Or you can try triggering an event or triggering change detection cycle manually and see that Angular updates DOM on the next cycle.

发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744178761a4561881.html

相关推荐

发表回复

评论列表(0条)

  • 暂无评论

联系我们

400-800-8888

在线咨询: QQ交谈

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信