Skip to content

Commit e9bedc6

Browse files
pkozlowski-opensourcemhevery
authored andcommitted
fix(ivy): properly query root nodes of embedded views(shallow queries) (angular#28560)
PR Close angular#28560
1 parent 7115e7c commit e9bedc6

File tree

9 files changed

+150
-89
lines changed

9 files changed

+150
-89
lines changed

packages/core/src/render3/instructions.ts

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,21 @@ export function elementContainerStart(
532532
const currentQueries = lView[QUERIES];
533533
if (currentQueries) {
534534
currentQueries.addNode(tNode);
535+
lView[QUERIES] = currentQueries.clone();
536+
}
537+
executeContentQueries(tView, tNode);
538+
}
539+
540+
function executeContentQueries(tView: TView, tNode: TNode) {
541+
if (isContentQueryHost(tNode)) {
542+
const start = tNode.directiveStart;
543+
const end = tNode.directiveEnd;
544+
for (let i = start; i < end; i++) {
545+
const def = tView.data[i] as DirectiveDef<any>;
546+
if (def.contentQueries) {
547+
def.contentQueries(i);
548+
}
549+
}
535550
}
536551
}
537552

@@ -543,16 +558,15 @@ export function elementContainerEnd(): void {
543558
if (getIsParent()) {
544559
setIsParent(false);
545560
} else {
546-
ngDevMode && assertHasParent(getPreviousOrParentTNode());
561+
ngDevMode && assertHasParent(previousOrParentTNode);
547562
previousOrParentTNode = previousOrParentTNode.parent !;
548563
setPreviousOrParentTNode(previousOrParentTNode);
549564
}
550565

551566
ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.ElementContainer);
552567
const currentQueries = lView[QUERIES];
553568
if (currentQueries) {
554-
lView[QUERIES] =
555-
isContentQueryHost(previousOrParentTNode) ? currentQueries.parent : currentQueries;
569+
lView[QUERIES] = currentQueries.parent;
556570
}
557571

558572
registerPostOrderHooks(tView, previousOrParentTNode);
@@ -630,7 +644,9 @@ export function elementStart(
630644
const currentQueries = lView[QUERIES];
631645
if (currentQueries) {
632646
currentQueries.addNode(tNode);
647+
lView[QUERIES] = currentQueries.clone();
633648
}
649+
executeContentQueries(tView, tNode);
634650
}
635651

636652
/**
@@ -668,17 +684,9 @@ function createDirectivesAndLocals(
668684
const previousOrParentTNode = getPreviousOrParentTNode();
669685
if (tView.firstTemplatePass) {
670686
ngDevMode && ngDevMode.firstTemplatePass++;
671-
672687
resolveDirectives(
673688
tView, lView, findDirectiveMatches(tView, lView, previousOrParentTNode),
674689
previousOrParentTNode, localRefs || null);
675-
} else {
676-
// During first template pass, queries are created or cloned when first requested
677-
// using `getOrCreateCurrentQueries`. For subsequent template passes, we clone
678-
// any current LQueries here up-front if the current node hosts a content query.
679-
if (isContentQueryHost(getPreviousOrParentTNode()) && lView[QUERIES]) {
680-
lView[QUERIES] = lView[QUERIES] !.clone();
681-
}
682690
}
683691
instantiateAllDirectives(tView, lView, previousOrParentTNode);
684692
invokeDirectivesHostBindings(tView, lView, previousOrParentTNode);
@@ -1068,8 +1076,7 @@ export function elementEnd(): void {
10681076
const lView = getLView();
10691077
const currentQueries = lView[QUERIES];
10701078
if (currentQueries) {
1071-
lView[QUERIES] =
1072-
isContentQueryHost(previousOrParentTNode) ? currentQueries.parent : currentQueries;
1079+
lView[QUERIES] = currentQueries.parent;
10731080
}
10741081

10751082
registerPostOrderHooks(getLView()[TVIEW], previousOrParentTNode);
@@ -1805,8 +1812,8 @@ function postProcessDirective<T>(
18051812
setInputsFromAttrs(directiveDefIdx, directive, def, previousOrParentTNode);
18061813
}
18071814

1808-
if (def.contentQueries) {
1809-
def.contentQueries(directiveDefIdx);
1815+
if (viewData[TVIEW].firstTemplatePass && def.contentQueries) {
1816+
previousOrParentTNode.flags |= TNodeFlags.hasContentQuery;
18101817
}
18111818

18121819
if (isComponentDef(def)) {
@@ -2191,7 +2198,7 @@ function containerInternal(
21912198
function addTContainerToQueries(lView: LView, tContainerNode: TContainerNode): void {
21922199
const queries = lView[QUERIES];
21932200
if (queries) {
2194-
lView[QUERIES] = queries.addNode(tContainerNode);
2201+
queries.addNode(tContainerNode);
21952202
const lContainer = lView[tContainerNode.index];
21962203
lContainer[QUERIES] = queries.container();
21972204
}

packages/core/src/render3/interfaces/query.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export interface LQueries {
3535
* Notify `LQueries` that a new `TNode` has been created and needs to be added to query results
3636
* if matching query predicate.
3737
*/
38-
addNode(tNode: TElementNode|TContainerNode|TElementContainerNode): LQueries|null;
38+
addNode(tNode: TElementNode|TContainerNode|TElementContainerNode): void;
3939

4040
/**
4141
* Notify `LQueries` that a new LContainer was added to ivy data structures. As a result we need

packages/core/src/render3/query.ts

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,8 @@ import {unusedValueExportToPlacateAjd as unused1} from './interfaces/definition'
2323
import {unusedValueExportToPlacateAjd as unused2} from './interfaces/injector';
2424
import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeType, unusedValueExportToPlacateAjd as unused3} from './interfaces/node';
2525
import {LQueries, unusedValueExportToPlacateAjd as unused4} from './interfaces/query';
26-
import {CONTENT_QUERIES, HEADER_OFFSET, LView, TVIEW} from './interfaces/view';
27-
import {getCurrentQueryIndex, getIsParent, getLView, getOrCreateCurrentQueries, setCurrentQueryIndex} from './state';
28-
import {isContentQueryHost} from './util';
26+
import {CONTENT_QUERIES, HEADER_OFFSET, LView, QUERIES, TVIEW} from './interfaces/view';
27+
import {getCurrentQueryIndex, getIsParent, getLView, setCurrentQueryIndex} from './state';
2928
import {createElementRef, createTemplateRef} from './view_engine_compatibility';
3029

3130
const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4;
@@ -107,7 +106,6 @@ export class LQueries_ implements LQueries {
107106
container(): LQueries|null {
108107
const shallowResults = copyQueriesToContainer(this.shallow);
109108
const deepResults = copyQueriesToContainer(this.deep);
110-
111109
return shallowResults || deepResults ? new LQueries_(this, shallowResults, deepResults) : null;
112110
}
113111

@@ -123,22 +121,9 @@ export class LQueries_ implements LQueries {
123121
insertView(index, this.deep);
124122
}
125123

126-
addNode(tNode: TElementNode|TContainerNode|TElementContainerNode): LQueries|null {
124+
addNode(tNode: TElementNode|TContainerNode|TElementContainerNode): void {
127125
add(this.deep, tNode);
128-
129-
if (isContentQueryHost(tNode)) {
130-
add(this.shallow, tNode);
131-
132-
if (tNode.parent && isContentQueryHost(tNode.parent)) {
133-
// if node has a content query and parent also has a content query
134-
// both queries need to check this node for shallow matches
135-
add(this.parent !.shallow, tNode);
136-
}
137-
return this.parent;
138-
}
139-
140-
isRootNodeOfQuery(tNode) && add(this.shallow, tNode);
141-
return this;
126+
add(this.shallow, tNode);
142127
}
143128

144129
removeView(): void {
@@ -147,10 +132,6 @@ export class LQueries_ implements LQueries {
147132
}
148133
}
149134

150-
function isRootNodeOfQuery(tNode: TNode) {
151-
return tNode.parent === null || isContentQueryHost(tNode.parent);
152-
}
153-
154135
function copyQueriesToContainer(query: LQuery<any>| null): LQuery<any>|null {
155136
let result: LQuery<any>|null = null;
156137

@@ -371,11 +352,12 @@ export function query<T>(
371352
// TODO: "read" should be an AbstractType (FW-486)
372353
predicate: Type<any>| string[], descend?: boolean, read?: any): QueryList<T> {
373354
ngDevMode && assertPreviousIsParent(getIsParent());
355+
const lView = getLView();
374356
const queryList = new QueryList<T>();
375-
const queries = getOrCreateCurrentQueries(LQueries_);
357+
const queries = lView[QUERIES] || (lView[QUERIES] = new LQueries_(null, null, null));
376358
(queryList as QueryList_<T>)._valuesTree = [];
377359
queries.track(queryList, predicate, descend, read);
378-
storeCleanupWithContext(getLView(), queryList, queryList.destroy);
360+
storeCleanupWithContext(lView, queryList, queryList.destroy);
379361
return queryList;
380362
}
381363

packages/core/src/render3/state.ts

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,8 @@ import {assertDefined} from '../util/assert';
1010

1111
import {executeHooks} from './hooks';
1212
import {ComponentDef, DirectiveDef} from './interfaces/definition';
13-
import {TElementNode, TNode, TNodeFlags, TViewNode} from './interfaces/node';
14-
import {LQueries} from './interfaces/query';
15-
import {BINDING_INDEX, CONTEXT, DECLARATION_VIEW, FLAGS, InitPhaseState, LView, LViewFlags, OpaqueViewState, QUERIES, TVIEW, T_HOST} from './interfaces/view';
16-
import {isContentQueryHost} from './util';
17-
13+
import {TElementNode, TNode, TViewNode} from './interfaces/node';
14+
import {BINDING_INDEX, CONTEXT, DECLARATION_VIEW, FLAGS, InitPhaseState, LView, LViewFlags, OpaqueViewState, TVIEW} from './interfaces/view';
1815

1916

2017
/**
@@ -165,28 +162,6 @@ export function setIsParent(value: boolean): void {
165162
isParent = value;
166163
}
167164

168-
/**
169-
* Query instructions can ask for "current queries" in 2 different cases:
170-
* - when creating view queries (at the root of a component view, before any node is created - in
171-
* this case currentQueries points to view queries)
172-
* - when creating content queries (i.e. this previousOrParentTNode points to a node on which we
173-
* create content queries).
174-
*/
175-
export function getOrCreateCurrentQueries(
176-
QueryType: {new (parent: null, shallow: null, deep: null): LQueries}): LQueries {
177-
const lView = getLView();
178-
let currentQueries = lView[QUERIES];
179-
// If this is the first content query on a node, any existing LQueries needs to be cloned.
180-
// In subsequent template passes, the cloning occurs before directive instantiation
181-
// in `createDirectivesAndLocals`.
182-
if (previousOrParentTNode && previousOrParentTNode !== lView[T_HOST] &&
183-
!isContentQueryHost(previousOrParentTNode)) {
184-
currentQueries && (currentQueries = lView[QUERIES] = currentQueries.clone());
185-
previousOrParentTNode.flags |= TNodeFlags.hasContentQuery;
186-
}
187-
188-
return currentQueries || (lView[QUERIES] = new QueryType(null, null, null));
189-
}
190165

191166
/** Checks whether a given view is in creation mode */
192167
export function isCreationMode(view: LView = lView): boolean {

packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,6 @@
5050
{
5151
"name": "HOST"
5252
},
53-
{
54-
"name": "T_HOST"
55-
},
5653
{
5754
"name": "INJECTOR"
5855
},
@@ -131,6 +128,9 @@
131128
{
132129
"name": "TVIEW"
133130
},
131+
{
132+
"name": "T_HOST"
133+
},
134134
{
135135
"name": "TriggerComponent"
136136
},
@@ -266,6 +266,9 @@
266266
{
267267
"name": "enterView"
268268
},
269+
{
270+
"name": "executeContentQueries"
271+
},
269272
{
270273
"name": "executeHooks"
271274
},

packages/core/test/bundling/todo/bundle.golden_symbols.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,6 @@
8080
{
8181
"name": "HOST"
8282
},
83-
{
84-
"name": "T_HOST"
85-
},
8683
{
8784
"name": "INJECTOR"
8885
},
@@ -218,6 +215,9 @@
218215
{
219216
"name": "TVIEW"
220217
},
218+
{
219+
"name": "T_HOST"
220+
},
221221
{
222222
"name": "TemplateRef"
223223
},
@@ -572,6 +572,9 @@
572572
{
573573
"name": "enterView"
574574
},
575+
{
576+
"name": "executeContentQueries"
577+
},
575578
{
576579
"name": "executeHooks"
577580
},

packages/core/test/forward_ref_integration_spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ class App {
4040
}
4141

4242
@Component({
43-
selector: 'lock',
44-
template: `{{frame.name}}(<span *ngFor="let lock of locks">{{lock.name}}</span>)`,
43+
selector: 'door',
44+
template: `{{frame.name}}(<span *ngFor="let lock of locks">{{lock.name}}</span>)`,
4545
})
4646
class Door {
4747
// TODO(issue/24571): remove '!'.

0 commit comments

Comments
 (0)