fix(ivy): refresh child components before executing ViewQuery function (#32922)
Child component refresh must happen before executing the ViewQueryFn because child components could insert a template from the host that contains the result of the ViewQuery function (see related test added in this PR). PR Close #32922
This commit is contained in:
parent
088435c5eb
commit
72f3747d7b
|
@ -430,17 +430,20 @@ export function refreshView<T>(
|
||||||
|
|
||||||
setHostBindings(tView, lView);
|
setHostBindings(tView, lView);
|
||||||
|
|
||||||
const viewQuery = tView.viewQuery;
|
|
||||||
if (viewQuery !== null) {
|
|
||||||
executeViewQueryFn(RenderFlags.Update, viewQuery, context);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Refresh child component views.
|
// Refresh child component views.
|
||||||
const components = tView.components;
|
const components = tView.components;
|
||||||
if (components !== null) {
|
if (components !== null) {
|
||||||
refreshChildComponents(lView, components);
|
refreshChildComponents(lView, components);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// View queries must execute after refreshing child components because a template in this view
|
||||||
|
// could be inserted in a child component. If the view query executes before child component
|
||||||
|
// refresh, the template might not yet be inserted.
|
||||||
|
const viewQuery = tView.viewQuery;
|
||||||
|
if (viewQuery !== null) {
|
||||||
|
executeViewQueryFn(RenderFlags.Update, viewQuery, context);
|
||||||
|
}
|
||||||
|
|
||||||
// execute view hooks (AfterViewInit, AfterViewChecked)
|
// execute view hooks (AfterViewInit, AfterViewChecked)
|
||||||
// PERF WARNING: do NOT extract this to a separate function without running benchmarks
|
// PERF WARNING: do NOT extract this to a separate function without running benchmarks
|
||||||
if (!checkNoChangesMode) {
|
if (!checkNoChangesMode) {
|
||||||
|
|
|
@ -265,6 +265,42 @@ describe('query logic', () => {
|
||||||
expect(fixture.componentInstance.foo.length).toBe(2);
|
expect(fixture.componentInstance.foo.length).toBe(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should support ViewChild query where template is inserted in child component', () => {
|
||||||
|
@Component({selector: 'required', template: ''})
|
||||||
|
class Required {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'insertion',
|
||||||
|
template: `<ng-container [ngTemplateOutlet]="content"></ng-container>`
|
||||||
|
})
|
||||||
|
class Insertion {
|
||||||
|
@Input() content !: TemplateRef<{}>;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: `
|
||||||
|
<ng-template #template>
|
||||||
|
<required></required>
|
||||||
|
</ng-template>
|
||||||
|
<insertion [content]="template"></insertion>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
class App {
|
||||||
|
@ViewChild(Required, {static: false}) requiredEl !: Required;
|
||||||
|
viewChildAvailableInAfterViewInit?: boolean;
|
||||||
|
|
||||||
|
ngAfterViewInit() {
|
||||||
|
this.viewChildAvailableInAfterViewInit = this.requiredEl !== undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const fixture = TestBed.configureTestingModule({declarations: [App, Insertion, Required]})
|
||||||
|
.createComponent(App);
|
||||||
|
fixture.detectChanges();
|
||||||
|
expect(fixture.componentInstance.viewChildAvailableInAfterViewInit).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('content queries', () => {
|
describe('content queries', () => {
|
||||||
|
|
Loading…
Reference in New Issue