fix(ivy): properly query root nodes of embedded views(shallow queries) (#28560)

PR Close #28560
This commit is contained in:
Pawel Kozlowski
2019-02-06 11:56:57 +01:00
committed by Miško Hevery
parent 7115e7c427
commit e9bedc63bb
9 changed files with 150 additions and 89 deletions

View File

@ -532,6 +532,21 @@ export function elementContainerStart(
const currentQueries = lView[QUERIES];
if (currentQueries) {
currentQueries.addNode(tNode);
lView[QUERIES] = currentQueries.clone();
}
executeContentQueries(tView, tNode);
}
function executeContentQueries(tView: TView, tNode: TNode) {
if (isContentQueryHost(tNode)) {
const start = tNode.directiveStart;
const end = tNode.directiveEnd;
for (let i = start; i < end; i++) {
const def = tView.data[i] as DirectiveDef<any>;
if (def.contentQueries) {
def.contentQueries(i);
}
}
}
}
@ -543,7 +558,7 @@ export function elementContainerEnd(): void {
if (getIsParent()) {
setIsParent(false);
} else {
ngDevMode && assertHasParent(getPreviousOrParentTNode());
ngDevMode && assertHasParent(previousOrParentTNode);
previousOrParentTNode = previousOrParentTNode.parent !;
setPreviousOrParentTNode(previousOrParentTNode);
}
@ -551,8 +566,7 @@ export function elementContainerEnd(): void {
ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.ElementContainer);
const currentQueries = lView[QUERIES];
if (currentQueries) {
lView[QUERIES] =
isContentQueryHost(previousOrParentTNode) ? currentQueries.parent : currentQueries;
lView[QUERIES] = currentQueries.parent;
}
registerPostOrderHooks(tView, previousOrParentTNode);
@ -630,7 +644,9 @@ export function elementStart(
const currentQueries = lView[QUERIES];
if (currentQueries) {
currentQueries.addNode(tNode);
lView[QUERIES] = currentQueries.clone();
}
executeContentQueries(tView, tNode);
}
/**
@ -668,17 +684,9 @@ function createDirectivesAndLocals(
const previousOrParentTNode = getPreviousOrParentTNode();
if (tView.firstTemplatePass) {
ngDevMode && ngDevMode.firstTemplatePass++;
resolveDirectives(
tView, lView, findDirectiveMatches(tView, lView, previousOrParentTNode),
previousOrParentTNode, localRefs || null);
} else {
// During first template pass, queries are created or cloned when first requested
// using `getOrCreateCurrentQueries`. For subsequent template passes, we clone
// any current LQueries here up-front if the current node hosts a content query.
if (isContentQueryHost(getPreviousOrParentTNode()) && lView[QUERIES]) {
lView[QUERIES] = lView[QUERIES] !.clone();
}
}
instantiateAllDirectives(tView, lView, previousOrParentTNode);
invokeDirectivesHostBindings(tView, lView, previousOrParentTNode);
@ -1068,8 +1076,7 @@ export function elementEnd(): void {
const lView = getLView();
const currentQueries = lView[QUERIES];
if (currentQueries) {
lView[QUERIES] =
isContentQueryHost(previousOrParentTNode) ? currentQueries.parent : currentQueries;
lView[QUERIES] = currentQueries.parent;
}
registerPostOrderHooks(getLView()[TVIEW], previousOrParentTNode);
@ -1805,8 +1812,8 @@ function postProcessDirective<T>(
setInputsFromAttrs(directiveDefIdx, directive, def, previousOrParentTNode);
}
if (def.contentQueries) {
def.contentQueries(directiveDefIdx);
if (viewData[TVIEW].firstTemplatePass && def.contentQueries) {
previousOrParentTNode.flags |= TNodeFlags.hasContentQuery;
}
if (isComponentDef(def)) {
@ -2191,7 +2198,7 @@ function containerInternal(
function addTContainerToQueries(lView: LView, tContainerNode: TContainerNode): void {
const queries = lView[QUERIES];
if (queries) {
lView[QUERIES] = queries.addNode(tContainerNode);
queries.addNode(tContainerNode);
const lContainer = lView[tContainerNode.index];
lContainer[QUERIES] = queries.container();
}

View File

@ -35,7 +35,7 @@ export interface LQueries {
* Notify `LQueries` that a new `TNode` has been created and needs to be added to query results
* if matching query predicate.
*/
addNode(tNode: TElementNode|TContainerNode|TElementContainerNode): LQueries|null;
addNode(tNode: TElementNode|TContainerNode|TElementContainerNode): void;
/**
* Notify `LQueries` that a new LContainer was added to ivy data structures. As a result we need

View File

@ -23,9 +23,8 @@ import {unusedValueExportToPlacateAjd as unused1} from './interfaces/definition'
import {unusedValueExportToPlacateAjd as unused2} from './interfaces/injector';
import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeType, unusedValueExportToPlacateAjd as unused3} from './interfaces/node';
import {LQueries, unusedValueExportToPlacateAjd as unused4} from './interfaces/query';
import {CONTENT_QUERIES, HEADER_OFFSET, LView, TVIEW} from './interfaces/view';
import {getCurrentQueryIndex, getIsParent, getLView, getOrCreateCurrentQueries, setCurrentQueryIndex} from './state';
import {isContentQueryHost} from './util';
import {CONTENT_QUERIES, HEADER_OFFSET, LView, QUERIES, TVIEW} from './interfaces/view';
import {getCurrentQueryIndex, getIsParent, getLView, setCurrentQueryIndex} from './state';
import {createElementRef, createTemplateRef} from './view_engine_compatibility';
const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4;
@ -107,7 +106,6 @@ export class LQueries_ implements LQueries {
container(): LQueries|null {
const shallowResults = copyQueriesToContainer(this.shallow);
const deepResults = copyQueriesToContainer(this.deep);
return shallowResults || deepResults ? new LQueries_(this, shallowResults, deepResults) : null;
}
@ -123,22 +121,9 @@ export class LQueries_ implements LQueries {
insertView(index, this.deep);
}
addNode(tNode: TElementNode|TContainerNode|TElementContainerNode): LQueries|null {
addNode(tNode: TElementNode|TContainerNode|TElementContainerNode): void {
add(this.deep, tNode);
if (isContentQueryHost(tNode)) {
add(this.shallow, tNode);
if (tNode.parent && isContentQueryHost(tNode.parent)) {
// if node has a content query and parent also has a content query
// both queries need to check this node for shallow matches
add(this.parent !.shallow, tNode);
}
return this.parent;
}
isRootNodeOfQuery(tNode) && add(this.shallow, tNode);
return this;
add(this.shallow, tNode);
}
removeView(): void {
@ -147,10 +132,6 @@ export class LQueries_ implements LQueries {
}
}
function isRootNodeOfQuery(tNode: TNode) {
return tNode.parent === null || isContentQueryHost(tNode.parent);
}
function copyQueriesToContainer(query: LQuery<any>| null): LQuery<any>|null {
let result: LQuery<any>|null = null;
@ -371,11 +352,12 @@ export function query<T>(
// TODO: "read" should be an AbstractType (FW-486)
predicate: Type<any>| string[], descend?: boolean, read?: any): QueryList<T> {
ngDevMode && assertPreviousIsParent(getIsParent());
const lView = getLView();
const queryList = new QueryList<T>();
const queries = getOrCreateCurrentQueries(LQueries_);
const queries = lView[QUERIES] || (lView[QUERIES] = new LQueries_(null, null, null));
(queryList as QueryList_<T>)._valuesTree = [];
queries.track(queryList, predicate, descend, read);
storeCleanupWithContext(getLView(), queryList, queryList.destroy);
storeCleanupWithContext(lView, queryList, queryList.destroy);
return queryList;
}

View File

@ -10,11 +10,8 @@ import {assertDefined} from '../util/assert';
import {executeHooks} from './hooks';
import {ComponentDef, DirectiveDef} from './interfaces/definition';
import {TElementNode, TNode, TNodeFlags, TViewNode} from './interfaces/node';
import {LQueries} from './interfaces/query';
import {BINDING_INDEX, CONTEXT, DECLARATION_VIEW, FLAGS, InitPhaseState, LView, LViewFlags, OpaqueViewState, QUERIES, TVIEW, T_HOST} from './interfaces/view';
import {isContentQueryHost} from './util';
import {TElementNode, TNode, TViewNode} from './interfaces/node';
import {BINDING_INDEX, CONTEXT, DECLARATION_VIEW, FLAGS, InitPhaseState, LView, LViewFlags, OpaqueViewState, TVIEW} from './interfaces/view';
/**
@ -165,28 +162,6 @@ export function setIsParent(value: boolean): void {
isParent = value;
}
/**
* Query instructions can ask for "current queries" in 2 different cases:
* - when creating view queries (at the root of a component view, before any node is created - in
* this case currentQueries points to view queries)
* - when creating content queries (i.e. this previousOrParentTNode points to a node on which we
* create content queries).
*/
export function getOrCreateCurrentQueries(
QueryType: {new (parent: null, shallow: null, deep: null): LQueries}): LQueries {
const lView = getLView();
let currentQueries = lView[QUERIES];
// If this is the first content query on a node, any existing LQueries needs to be cloned.
// In subsequent template passes, the cloning occurs before directive instantiation
// in `createDirectivesAndLocals`.
if (previousOrParentTNode && previousOrParentTNode !== lView[T_HOST] &&
!isContentQueryHost(previousOrParentTNode)) {
currentQueries && (currentQueries = lView[QUERIES] = currentQueries.clone());
previousOrParentTNode.flags |= TNodeFlags.hasContentQuery;
}
return currentQueries || (lView[QUERIES] = new QueryType(null, null, null));
}
/** Checks whether a given view is in creation mode */
export function isCreationMode(view: LView = lView): boolean {