简体   繁体   中英

Angular QueryList change event ExpressionChangedAfterItHasBeenCheckedError

When subscribing to a QueryList change events and when reacting to the event and changing a property that the view is bound to. I get a ExpressionChangedAfterItHasBeenCheckedError and the value is like the old value.

I made a stackblitz example show casing the error.

I can solve it by adding a microtask to the queue. But I'm just trying to understand what is happening.

Thanks

here is what happens;

in dev mode, two consecutive CD cycles happen as explained in Relevant change detection operations section of this article;

A running Angular application is a tree of components. During change detection Angular performs checks for each component which consists of the following operations performed in the specified order:

  1. update bound properties for all child components/directives
  2. call ngOnInit, OnChanges and ngDoCheck lifecycle hooks on all child components/directives
  3. update DOM for the current component
  4. run change detection for a child component
  5. call ngAfterViewInit lifecycle hook for all child components/directives
  • after you click add button, when click callback finishes executing change detection begins. comp has 1 element
  • during the first CD cycle; DOM is updated at step 3. elements in comp added to DOM and {{ count.length }} is projected to DOM as 0
  • @ViewChildren('comp') test: QueryList<ElementRef> is updated just before step 5. test: QueryList has 1 element
  • QueryList.changes observable emits at the same time when ViewChildren query is set, just before step 5. this.count.push(this.test.length) gets executed and count.length becomes 1.
  • ngAfterViewChecked is executed and first CD (relevant part of our investigation) ends
  • 2nd CD cycle begins. during this cycle angular checks previous values of 1st CD cycle with current values.
  • During this check it encounters that {{ count.length }} was projected to DOM as 0 but now count.length is 1. It throws exception at this point.

To explain more; changing {{ count.length }} to {{ count }} doesn't throw error because object reference didn't change.

Similarly; changing {{ count.length }} to {{ comps.length }} also doesn't throw error because comps.length was also 1 before 1st CD cycle begins.

I also created a demo that prints relevant steps during component lifecycle related to our investigation. You can see that exception is thrown before 4th CD cycle begins. (1st and 2nd CD cycles happen before button click so in demo 3rd and 4th CD cycles should be observed). Also pay attention to the point where QueryList.changes emits value.

https://stackblitz.com/edit/angular-tmcttm

Finally, as you said, the solution to this particular problem is to add a microtask to the queue by changing this line

this.count.push(this.test.length);

into this

setTimeout(_ => this.count.push(this.test.length));

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM