-
Notifications
You must be signed in to change notification settings - Fork 27.1k
Description
Which @angular/* package(s) are the source of the bug?
core
Is this a regression?
No
Description
Traditionally only @ViewChild and @ContentChild has had a static option.
@ViewChildren and @ContentChildren do not, however there isn't really a good reason given. The best information I could find was:
@ViewChildren and @ContentChildren queries are already "dynamic" by default and don't support static resolution.
Traditionally the static option is useful to developers because it allows for the value of @ViewChild to be consumed in ngOnInit. There is also an additional performance benefit too since it only ever gets calculated once, even if for most components this would be negligible.
There is another (deprecated) option 'emitDistinctChangesOnly' for @ViewChildren](https://angular.io/api/core/ViewChildren) intended to only emit a new if the QueryList has actually changed. I haven't looked into the details of this, but I'm assuming it does some kind of identity comparison for items in the QueryList.
When playing with the new viewChildren() signal in Angular 17.2 I found that the signal yields duplicate values even if the list has not changed. You can get an infinite loop if this value is used in certain ways in the template because Angular keeps recalculating it:
Error: NG0103: Infinite change detection while trying to refresh views.
In the attached example my app component behaves as a custom grid control. There are a number of <ng-template column-template></ng-template> columns defined, that are captured to a signal via viewChildren(ColumnTemplateDirective). In my real project the component uses mat-table but in this example I just output the list of columns.
The problem is that when stamping out the columns new views are created, which causes viewChildren to be recalculated. Even though the list of ColumnTemplateDirective is unchanged it 'emits' a new value causing the infinite loop.
Notes about my repro
Open stackblitz console to see the error
My reproduction of this issue can easily be 'fixed' and I am well aware of ways in which it could be.
- I could create a
computedwith a custom equals that takes theviewChildrenlist and ignores duplicate values. - The
@forin my repro that iterates through thecolumnscomputed signal uses object identity. If changed totrack col.idthen the problem goes away because the template inside doesn't keep getting recreated.
I don't know exactly what triggers viewChildren to recalculate itself, but I had to add an extra 'scope' with @if (true) in order to trigger it to do so.
Frankly it was harder to create this repro than I expected. It was much easier with mat-table to trigger the infinite loop, but I wanted to stay within core for the repro.
Important
Ultimately what I would hope to see is two things:
- Identity checking of elements returned from
viewChildren()such that unchanged children do not update the signal value. - A way to use something like
staticforviewChildren()in order to only calculate the list one time. I don't think it would make sense forcontentChildren(). This would primarily be for performance reasons when needing to captureviewChildrenthat are known to be static. This was easier before withQueryListthat was an observable.
I can easily work around the duplicate viewChildren value, but I am more concerned about performance especially since my component displays a grid with thousands of cells and Angular is probably continuously looking through the whole table for new templates.
Please provide a link to a minimal reproduction of the bug
https://stackblitz.com/edit/angular-model-inputs-mv3bsn
Please provide the exception or error you saw
Error: NG0103: Infinite change detection while trying to refresh views.
Please provide the environment you discovered this bug in (run ng version)
Angular: 17.2.0-rc.1
Anything else?
No response