From d9a1a7dd07497768b1c70fe698b1547bd1f8488e Mon Sep 17 00:00:00 2001 From: Pawel Kozlowski Date: Thu, 15 Feb 2024 11:26:33 +0100 Subject: [PATCH] fix(core): properly execute content queries for root components (#54457) Prior to this fix an incorrect view instance (a dynamically created component one instead of the root view) was passed to the content query function. Having incorrect view instance meant that a component instance could not be found. This is a pre-existing bug, introduction of signal-based queries just surfaced it. Fixes #54450 PR Close #54457 --- packages/core/src/render3/component_ref.ts | 3 +- .../core/src/render3/instructions/shared.ts | 10 ++++-- .../authoring/signal_queries_spec.ts | 36 ++++++++++++++++++- 3 files changed, 43 insertions(+), 6 deletions(-) diff --git a/packages/core/src/render3/component_ref.ts b/packages/core/src/render3/component_ref.ts index a168834c0f3c7..dda0c817d2ec7 100644 --- a/packages/core/src/render3/component_ref.ts +++ b/packages/core/src/render3/component_ref.ts @@ -46,7 +46,6 @@ import {CONTEXT, HEADER_OFFSET, INJECTOR, LView, LViewEnvironment, LViewFlags, T import {MATH_ML_NAMESPACE, SVG_NAMESPACE} from './namespaces'; import {createElementNode, setupStaticAttributes, writeDirectClass} from './node_manipulation'; import {extractAttrsAndClassesFromSelector, stringifyCSSSelectorList} from './node_selector_matcher'; -import {EffectScheduler} from './reactivity/effect'; import {enterView, getCurrentTNode, getLView, leaveView} from './state'; import {computeStaticStyling} from './styling/static_styling'; import {mergeHostAttrs, setUpAttributes} from './util/attrs_utils'; @@ -504,7 +503,7 @@ function createRootComponent( // We want to generate an empty QueryList for root content queries for backwards // compatibility with ViewEngine. - executeContentQueries(tView, rootTNode, componentView); + executeContentQueries(tView, rootTNode, rootLView); return component; } diff --git a/packages/core/src/render3/instructions/shared.ts b/packages/core/src/render3/instructions/shared.ts index 1a0045b56b3c2..55003b7b3af8b 100644 --- a/packages/core/src/render3/instructions/shared.ts +++ b/packages/core/src/render3/instructions/shared.ts @@ -30,9 +30,11 @@ import {attachPatchData} from '../context_discovery'; import {getFactoryDef} from '../definition_factory'; import {diPublicInInjector, getNodeInjectable, getOrCreateNodeInjectorForNode} from '../di'; import {throwMultipleComponentError} from '../errors'; +import {AttributeMarker} from '../interfaces/attribute_marker'; import {CONTAINER_HEADER_OFFSET, LContainer} from '../interfaces/container'; import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, HostBindingsFunction, HostDirectiveBindingMap, HostDirectiveDefs, PipeDefListOrFactory, RenderFlags, ViewQueriesFunction} from '../interfaces/definition'; import {NodeInjectorFactory} from '../interfaces/injector'; +import {InputFlags} from '../interfaces/input_flags'; import {getUniqueLViewId} from '../interfaces/lview_tracking'; import {InitialInputData, InitialInputs, LocalRefExtractor, NodeInputBindings, NodeOutputBindings, TAttributes, TConstantsOrFactory, TContainerNode, TDirectiveHostNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeType, TProjectionNode} from '../interfaces/node'; import {Renderer} from '../interfaces/renderer'; @@ -56,8 +58,6 @@ import {selectIndexInternal} from './advance'; import {ɵɵdirectiveInject} from './di'; import {handleUnknownPropertyError, isPropertyValid, matchingSchemas} from './element_validation'; import {writeToDirectiveInput} from './write_to_directive_input'; -import { AttributeMarker } from '../interfaces/attribute_marker'; -import { InputFlags } from '../interfaces/input_flags'; /** * Invoke `HostBindingsFunction`s for view. @@ -287,7 +287,11 @@ export function executeContentQueries(tView: TView, tNode: TNode, lView: LView) for (let directiveIndex = start; directiveIndex < end; directiveIndex++) { const def = tView.data[directiveIndex] as DirectiveDef; if (def.contentQueries) { - def.contentQueries(RenderFlags.Create, lView[directiveIndex], directiveIndex); + const directiveInstance = lView[directiveIndex]; + ngDevMode && + assertDefined( + directiveIndex, 'Incorrect reference to a directive defining a content query'); + def.contentQueries(RenderFlags.Create, directiveInstance, directiveIndex); } } } finally { diff --git a/packages/core/test/acceptance/authoring/signal_queries_spec.ts b/packages/core/test/acceptance/authoring/signal_queries_spec.ts index 3b070b6664b44..11a110eae52c7 100644 --- a/packages/core/test/acceptance/authoring/signal_queries_spec.ts +++ b/packages/core/test/acceptance/authoring/signal_queries_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {Component, computed, contentChild, contentChildren, Directive, ElementRef, viewChild, viewChildren} from '@angular/core'; +import {Component, computed, contentChild, contentChildren, createComponent, Directive, ElementRef, EnvironmentInjector, viewChild, viewChildren} from '@angular/core'; import {TestBed} from '@angular/core/testing'; import {By} from '@angular/platform-browser'; @@ -441,4 +441,38 @@ describe('queries as signals', () => { expect(recomputeCount).toBe(1); }); }); + + describe('dynamic component creation', () => { + it('should return empty results for content queries of dynamically created components', () => { + // https://github.com/angular/angular/issues/54450 + @Component({ + selector: 'query-cmp', + standalone: true, + template: ``, + }) + class QueryComponent { + elements = contentChildren('el'); + element = contentChild('el'); + } + + @Component({ + standalone: true, + template: ``, + }) + class TestComponent { + constructor(private _envInjector: EnvironmentInjector) {} + + createDynamic() { + return createComponent(QueryComponent, {environmentInjector: this._envInjector}); + } + } + + const fixture = TestBed.createComponent(TestComponent); + const cmpRef = fixture.componentInstance.createDynamic(); + cmpRef.changeDetectorRef.detectChanges(); + + expect(cmpRef.instance.elements()).toEqual([]); + expect(cmpRef.instance.element()).toBeUndefined(); + }); + }); });