perf(ivy): match query results on the TView level (#31489)

PR Close #31489
This commit is contained in:
Pawel Kozlowski 2019-06-07 10:55:48 +02:00 committed by Kara Erickson
parent 9c954ebc62
commit d52ae7cbab
20 changed files with 1001 additions and 1036 deletions

View File

@ -119,7 +119,7 @@ export class QueryList<T>/* implements Iterable<T> */ {
* on change detection, it will not notify of changes to the queries, unless a new change
* occurs.
*
* @param resultsTree The results tree to store
* @param resultsTree The query results to store
*/
reset(resultsTree: Array<T|any[]>): void {
this._results = flatten(resultsTree);

View File

@ -69,3 +69,7 @@ export function assertLView(value: any) {
assertDefined(value, 'LView must be defined');
assertEqual(isLView(value), true, 'Expecting LView');
}
export function assertFirstTemplatePass(tView: TView, errMessage: string) {
assertEqual(tView.firstTemplatePass, true, errMessage);
}

View File

@ -11,7 +11,7 @@
import {Type} from '../core';
import {Injector} from '../di/injector';
import {Sanitizer} from '../sanitization/security';
import {assertDataInRange, assertEqual} from '../util/assert';
import {assertDataInRange} from '../util/assert';
import {assertComponentType} from './assert';
import {getComponentDef} from './definition';

View File

@ -12,13 +12,14 @@ import {executePreOrderHooks, registerPostOrderHooks} from '../hooks';
import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer} from '../interfaces/container';
import {ComponentTemplate} from '../interfaces/definition';
import {LocalRefExtractor, TAttributes, TContainerNode, TNode, TNodeType} from '../interfaces/node';
import {BINDING_INDEX, HEADER_OFFSET, LView, QUERIES, RENDERER, TVIEW, T_HOST} from '../interfaces/view';
import {BINDING_INDEX, HEADER_OFFSET, LView, RENDERER, TVIEW, T_HOST} from '../interfaces/view';
import {assertNodeType} from '../node_assert';
import {appendChild, removeView} from '../node_manipulation';
import {getCheckNoChangesMode, getIsParent, getLView, getPreviousOrParentTNode, setIsNotParent, setPreviousOrParentTNode} from '../state';
import {getNativeByTNode, loadInternal} from '../util/view_utils';
import {addToViewTree, createDirectivesAndLocals, createLContainer, createTView, getOrCreateTNode} from './shared';
import {addToViewTree, createDirectivesAndLocals, createLContainer, createTView, getOrCreateTNode, resolveDirectives} from './shared';
/**
@ -39,7 +40,6 @@ export function ɵɵcontainer(index: number): void {
if (lView[TVIEW].firstTemplatePass) {
tNode.tViews = [];
}
addTContainerToQueries(lView, tNode);
setIsNotParent();
}
@ -72,12 +72,18 @@ export function ɵɵtemplate(
// TODO: consider a separate node type for templates
const tContainerNode = containerInternal(lView, index, tagName || null, attrs || null);
if (tView.firstTemplatePass) {
tContainerNode.tViews = createTView(
ngDevMode && ngDevMode.firstTemplatePass++;
resolveDirectives(tView, lView, tContainerNode, localRefs || null);
const embeddedTView = tContainerNode.tViews = createTView(
-1, templateFn, consts, vars, tView.directiveRegistry, tView.pipeRegistry, null, null);
if (tView.queries !== null) {
tView.queries.template(tView, tContainerNode);
embeddedTView.queries = tView.queries.embeddedTView(tContainerNode);
}
}
createDirectivesAndLocals(tView, lView, tContainerNode, localRefs, localRefExtractor);
addTContainerToQueries(lView, tContainerNode);
createDirectivesAndLocals(tView, lView, tContainerNode, localRefExtractor);
attachPatchData(getNativeByTNode(tContainerNode, lView), lView);
registerPostOrderHooks(tView, tContainerNode);
setIsNotParent();
@ -133,32 +139,6 @@ export function ɵɵcontainerRefreshEnd(): void {
}
}
/**
* Reporting a TContainer node queries is a 2-step process as we need to:
* - check if the container node itself is matching (query might match a <ng-template> node);
* - prepare room for nodes from views that might be created based on the TemplateRef linked to this
* container.
*
* Those 2 operations need to happen in the specific order (match the container node itself, then
* prepare space for nodes from views).
*/
function addTContainerToQueries(lView: LView, tContainerNode: TContainerNode): void {
const queries = lView[QUERIES];
if (queries) {
const lContainer = lView[tContainerNode.index];
if (lContainer[QUERIES]) {
// Query container should only exist if it was created through a dynamic view
// in a directive constructor. In this case, we must splice the template
// matches in before the view matches to ensure query results in embedded views
// don't clobber query results on the template node itself.
queries.insertNodeBeforeViews(tContainerNode);
} else {
queries.addNode(tContainerNode);
lContainer[QUERIES] = queries.container();
}
}
}
function containerInternal(
lView: LView, nodeIndex: number, tagName: string | null,
attrs: TAttributes | null): TContainerNode {

View File

@ -5,13 +5,15 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {assertDataInRange, assertDefined, assertEqual} from '../../util/assert';
import {assertHasParent} from '../assert';
import {attachPatchData} from '../context_discovery';
import {registerPostOrderHooks} from '../hooks';
import {PropertyAliases, TAttributes, TNode, TNodeFlags, TNodeType} from '../interfaces/node';
import {TAttributes, TNodeFlags, TNodeType} from '../interfaces/node';
import {RElement} from '../interfaces/renderer';
import {BINDING_INDEX, HEADER_OFFSET, LView, QUERIES, RENDERER, TVIEW, T_HOST} from '../interfaces/view';
import {isContentQueryHost} from '../interfaces/type_checks';
import {BINDING_INDEX, HEADER_OFFSET, LView, RENDERER, TVIEW, T_HOST} from '../interfaces/view';
import {assertNodeType} from '../node_assert';
import {appendChild} from '../node_manipulation';
import {applyOnCreateInstructions} from '../node_util';
@ -22,7 +24,7 @@ import {getInitialStylingValue, hasClassInput, hasStyleInput} from '../styling_n
import {setUpAttributes} from '../util/attrs_utils';
import {getNativeByTNode, getTNode} from '../util/view_utils';
import {createDirectivesAndLocals, elementCreate, executeContentQueries, getOrCreateTNode, initializeTNodeInputs, renderInitialStyling, setInputsForProperty} from './shared';
import {createDirectivesAndLocals, elementCreate, executeContentQueries, getOrCreateTNode, initializeTNodeInputs, renderInitialStyling, resolveDirectives, setInputsForProperty} from './shared';
@ -66,7 +68,6 @@ export function ɵɵelementStart(
renderInitialStyling(renderer, native, tNode);
appendChild(native, tNode, lView);
createDirectivesAndLocals(tView, lView, tNode, localRefs);
// any immediate children of a component or template container must be pre-emptively
// monkey-patched with the component view data so that the element can be inspected
@ -81,6 +82,9 @@ export function ɵɵelementStart(
// static class values as well. (Note that this will be fixed once map-based `[style]`
// and `[class]` bindings work for multiple directives.)
if (tView.firstTemplatePass) {
ngDevMode && ngDevMode.firstTemplatePass++;
resolveDirectives(tView, lView, tNode, localRefs || null);
const inputData = initializeTNodeInputs(tNode);
if (inputData && inputData.hasOwnProperty('class')) {
tNode.flags |= TNodeFlags.hasClassInput;
@ -89,13 +93,13 @@ export function ɵɵelementStart(
if (inputData && inputData.hasOwnProperty('style')) {
tNode.flags |= TNodeFlags.hasStyleInput;
}
if (tView.queries !== null) {
tView.queries.elementStart(tView, tNode);
}
}
const currentQueries = lView[QUERIES];
if (currentQueries) {
currentQueries.addNode(tNode);
lView[QUERIES] = currentQueries.clone(tNode);
}
createDirectivesAndLocals(tView, lView, tNode);
executeContentQueries(tView, tNode, lView);
}
@ -123,15 +127,16 @@ export function ɵɵelementEnd(): void {
ngDevMode && assertNodeType(tNode, TNodeType.Element);
const lView = getLView();
const currentQueries = lView[QUERIES];
// Go back up to parent queries only if queries have been cloned on this element.
if (currentQueries && tNode.index === currentQueries.nodeIndex) {
lView[QUERIES] = currentQueries.parent;
}
const tView = lView[TVIEW];
registerPostOrderHooks(lView[TVIEW], tNode);
registerPostOrderHooks(tView, previousOrParentTNode);
decreaseElementDepthCount();
if (tView.firstTemplatePass && tView.queries !== null &&
isContentQueryHost(previousOrParentTNode)) {
tView.queries !.elementEnd(previousOrParentTNode);
}
if (hasClassInput(tNode) && tNode.classes) {
setDirectiveStylingInput(tNode.classes, lView, tNode.inputs !['class']);
}

View File

@ -10,14 +10,15 @@ import {assertHasParent} from '../assert';
import {attachPatchData} from '../context_discovery';
import {registerPostOrderHooks} from '../hooks';
import {TAttributes, TNodeType} from '../interfaces/node';
import {BINDING_INDEX, HEADER_OFFSET, QUERIES, RENDERER, TVIEW, T_HOST} from '../interfaces/view';
import {isContentQueryHost} from '../interfaces/type_checks';
import {BINDING_INDEX, HEADER_OFFSET, RENDERER, TVIEW, T_HOST} from '../interfaces/view';
import {assertNodeType} from '../node_assert';
import {appendChild} from '../node_manipulation';
import {applyOnCreateInstructions} from '../node_util';
import {getIsParent, getLView, getPreviousOrParentTNode, setIsNotParent, setPreviousOrParentTNode} from '../state';
import {registerInitialStylingOnTNode} from '../styling_next/instructions';
import {createDirectivesAndLocals, executeContentQueries, getOrCreateTNode} from './shared';
import {createDirectivesAndLocals, executeContentQueries, getOrCreateTNode, resolveDirectives} from './shared';
@ -61,14 +62,17 @@ export function ɵɵelementContainerStart(
}
appendChild(native, tNode, lView);
createDirectivesAndLocals(tView, lView, tNode, localRefs);
attachPatchData(native, lView);
const currentQueries = lView[QUERIES];
if (currentQueries) {
currentQueries.addNode(tNode);
lView[QUERIES] = currentQueries.clone(tNode);
if (tView.firstTemplatePass) {
ngDevMode && ngDevMode.firstTemplatePass++;
resolveDirectives(tView, lView, tNode, localRefs || null);
if (tView.queries) {
tView.queries.elementStart(tView, tNode);
}
}
createDirectivesAndLocals(tView, lView, tNode);
attachPatchData(native, lView);
executeContentQueries(tView, tNode, lView);
}
@ -90,17 +94,17 @@ export function ɵɵelementContainerEnd(): void {
}
ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.ElementContainer);
const currentQueries = lView[QUERIES];
// Go back up to parent queries only if queries have been cloned on this element.
if (currentQueries && previousOrParentTNode.index === currentQueries.nodeIndex) {
lView[QUERIES] = currentQueries.parent;
}
// this is required for all host-level styling-related instructions to run
// in the correct order
previousOrParentTNode.onElementCreationFns && applyOnCreateInstructions(previousOrParentTNode);
registerPostOrderHooks(tView, previousOrParentTNode);
if (tView.firstTemplatePass && tView.queries !== null &&
isContentQueryHost(previousOrParentTNode)) {
tView.queries.elementEnd(previousOrParentTNode);
}
}
/**

View File

@ -49,10 +49,6 @@ export function ɵɵembeddedViewStart(
getOrCreateEmbeddedTView(viewBlockId, consts, vars, containerTNode as TContainerNode), null,
LViewFlags.CheckAlways, null, null);
if (lContainer[QUERIES]) {
viewToRender[QUERIES] = lContainer[QUERIES] !.createView();
}
const tParentNode = getIsParent() ? previousOrParentTNode :
previousOrParentTNode && previousOrParentTNode.parent;
assignTViewNodeToLView(viewToRender[TVIEW], tParentNode, viewBlockId, viewToRender);

View File

@ -10,15 +10,16 @@ import {AttributeMarker, ComponentTemplate} from '..';
import {SchemaMetadata} from '../../core';
import {assertDefined} from '../../util/assert';
import {createNamedArrayType} from '../../util/named_array_type';
import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer, NATIVE} from '../interfaces/container';
import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer, MOVED_VIEWS, NATIVE} from '../interfaces/container';
import {DirectiveDefList, PipeDefList, ViewQueriesFunction} from '../interfaces/definition';
import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, TIcu} from '../interfaces/i18n';
import {PropertyAliases, TContainerNode, TElementNode, TNode as ITNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TViewNode} from '../interfaces/node';
import {SelectorFlags} from '../interfaces/projection';
import {LQueries} from '../interfaces/query';
import {TQueries} from '../interfaces/query';
import {RComment, RElement, RNode} from '../interfaces/renderer';
import {StylingContext} from '../interfaces/styling';
import {BINDING_INDEX, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTENT_QUERIES, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, HookData, INJECTOR, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, SANITIZER, TData, TVIEW, TView as ITView, TView, T_HOST} from '../interfaces/view';
import {BINDING_INDEX, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, HookData, INJECTOR, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, SANITIZER, TData, TVIEW, TView as ITView, TView, T_HOST} from '../interfaces/view';
import {TStylingContext} from '../styling_next/interfaces';
import {DebugStyling as DebugNewStyling, NodeStylingDebug} from '../styling_next/styling_debug';
import {isStylingContext} from '../styling_next/util';
@ -78,11 +79,11 @@ export const TViewConstructor = class TView implements ITView {
public id: number, //
public blueprint: LView, //
public template: ComponentTemplate<{}>|null, //
public queries: TQueries|null, //
public viewQuery: ViewQueriesFunction<{}>|null, //
public node: TViewNode|TElementNode|null, //
public data: TData, //
public bindingStartIndex: number, //
public viewQueryStartIndex: number, //
public expandoStartIndex: number, //
public expandoInstructions: ExpandoInstructions|null, //
public firstTemplatePass: boolean, //
@ -287,8 +288,7 @@ export class LViewDebug {
next: toDebug(this._raw_lView[NEXT]),
childTail: toDebug(this._raw_lView[CHILD_TAIL]),
declarationView: toDebug(this._raw_lView[DECLARATION_VIEW]),
contentQueries: this._raw_lView[CONTENT_QUERIES],
queries: this._raw_lView[QUERIES],
queries: null,
tHost: this._raw_lView[T_HOST],
bindingIndex: this._raw_lView[BINDING_INDEX],
};
@ -361,7 +361,7 @@ export class LContainerDebug {
.map(toDebug as(l: LView) => LViewDebug);
}
get parent(): LViewDebug|LContainerDebug|null { return toDebug(this._raw_lContainer[PARENT]); }
get queries(): LQueries|null { return this._raw_lContainer[QUERIES]; }
get movedViews(): LView[]|null { return this._raw_lContainer[MOVED_VIEWS]; }
get host(): RElement|RComment|StylingContext|LView { return this._raw_lContainer[HOST]; }
get native(): RComment { return this._raw_lContainer[NATIVE]; }
get __other__() {

View File

@ -23,7 +23,6 @@ import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer} from '../interfaces/c
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, FactoryFn, PipeDefListOrFactory, RenderFlags, ViewQueriesFunction} from '../interfaces/definition';
import {INJECTOR_BLOOM_PARENT_SIZE, NodeInjectorFactory} from '../interfaces/injector';
import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TProjectionNode, TViewNode} from '../interfaces/node';
import {LQueries} from '../interfaces/query';
import {RComment, RElement, RText, Renderer3, RendererFactory3, isProceduralRenderer} from '../interfaces/renderer';
import {SanitizerFn} from '../interfaces/sanitization';
import {StylingContext} from '../interfaces/styling';
@ -157,16 +156,20 @@ export function setHostBindings(tView: TView, viewData: LView): void {
}
}
/** Refreshes content queries for all directives in the given view. */
/** Refreshes all content queries declared by directives in a given view */
function refreshContentQueries(tView: TView, lView: LView): void {
if (tView.contentQueries != null) {
setCurrentQueryIndex(0);
for (let i = 0; i < tView.contentQueries.length; i++) {
const directiveDefIdx = tView.contentQueries[i];
const directiveDef = tView.data[directiveDefIdx] as DirectiveDef<any>;
ngDevMode &&
assertDefined(directiveDef.contentQueries, 'contentQueries function should be defined');
directiveDef.contentQueries !(RenderFlags.Update, lView[directiveDefIdx], directiveDefIdx);
const contentQueries = tView.contentQueries;
if (contentQueries !== null) {
for (let i = 0; i < contentQueries.length; i += 2) {
const queryStartIdx = contentQueries[i];
const directiveDefIdx = contentQueries[i + 1];
if (directiveDefIdx !== -1) {
const directiveDef = tView.data[directiveDefIdx] as DirectiveDef<any>;
ngDevMode &&
assertDefined(directiveDef.contentQueries, 'contentQueries function should be defined');
setCurrentQueryIndex(queryStartIdx);
directiveDef.contentQueries !(RenderFlags.Update, lView[directiveDefIdx], directiveDefIdx);
}
}
}
}
@ -361,8 +364,7 @@ export function allocExpando(view: LView, numSlotsToAlloc: number) {
* Such lViewNode will then be renderer with renderEmbeddedTemplate() (see below).
*/
export function createEmbeddedViewAndNode<T>(
tView: TView, context: T, declarationView: LView, queries: LQueries | null,
injectorIndex: number): LView {
tView: TView, context: T, declarationView: LView, injectorIndex: number): LView {
const _isParent = getIsParent();
const _previousOrParentTNode = getPreviousOrParentTNode();
setPreviousOrParentTNode(null !, true);
@ -370,9 +372,6 @@ export function createEmbeddedViewAndNode<T>(
const lView = createLView(declarationView, tView, context, LViewFlags.CheckAlways, null, null);
lView[DECLARATION_VIEW] = declarationView;
if (queries) {
lView[QUERIES] = queries.createView();
}
assignTViewNodeToLView(tView, null, -1, lView);
if (tView.firstTemplatePass) {
@ -513,14 +512,8 @@ export function executeContentQueries(tView: TView, tNode: TNode, lView: LView)
*/
export function createDirectivesAndLocals(
tView: TView, lView: LView, tNode: TElementNode | TContainerNode | TElementContainerNode,
localRefs: string[] | null | undefined,
localRefExtractor: LocalRefExtractor = getNativeByTNode) {
if (!getBindingsEnabled()) return;
if (tView.firstTemplatePass) {
ngDevMode && ngDevMode.firstTemplatePass++;
resolveDirectives(
tView, lView, findDirectiveMatches(tView, lView, tNode), tNode, localRefs || null);
}
instantiateAllDirectives(tView, lView, tNode);
invokeDirectivesHostBindings(tView, lView, tNode);
saveResolvedLocalsInData(lView, tNode, localRefExtractor);
@ -588,11 +581,11 @@ export function createTView(
viewIndex, // id: number,
blueprint, // blueprint: LView,
templateFn, // template: ComponentTemplate<{}>|null,
null, // queries: TQueries|null
viewQuery, // viewQuery: ViewQueriesFunction<{}>|null,
null !, // node: TViewNode|TElementNode|null,
cloneToTViewData(blueprint).fill(null, bindingStartIndex), // data: TData,
bindingStartIndex, // bindingStartIndex: number,
initialViewLength, // viewQueryStartIndex: number,
initialViewLength, // expandoStartIndex: number,
null, // expandoInstructions: ExpandoInstructions|null,
true, // firstTemplatePass: boolean,
@ -619,11 +612,11 @@ export function createTView(
id: viewIndex,
blueprint: blueprint,
template: templateFn,
queries: null,
viewQuery: viewQuery,
node: null !,
data: blueprint.slice().fill(null, bindingStartIndex),
bindingStartIndex: bindingStartIndex,
viewQueryStartIndex: initialViewLength,
expandoStartIndex: initialViewLength,
expandoInstructions: null,
firstTemplatePass: true,
@ -1031,13 +1024,18 @@ export function instantiateRootComponent<T>(
/**
* Resolve the matched directives on a node.
*/
function resolveDirectives(
tView: TView, viewData: LView, directives: DirectiveDef<any>[] | null, tNode: TNode,
export function resolveDirectives(
tView: TView, lView: LView, tNode: TElementNode | TContainerNode | TElementContainerNode,
localRefs: string[] | null): void {
// Please make sure to have explicit type for `exportsMap`. Inferred type triggers bug in
// tsickle.
ngDevMode && assertEqual(tView.firstTemplatePass, true, 'should run on first template pass only');
if (!getBindingsEnabled()) return;
const directives: DirectiveDef<any>[]|null = findDirectiveMatches(tView, lView, tNode);
const exportsMap: ({[key: string]: number} | null) = localRefs ? {'': -1} : null;
if (directives) {
initNodeFlags(tNode, tView.data.length, directives.length);
// When the same token is provided by several directives on the same node, some rules apply in
@ -1059,7 +1057,7 @@ function resolveDirectives(
const def = directives[i] as DirectiveDef<any>;
const directiveDefIdx = tView.data.length;
baseResolveDirective(tView, viewData, def, def.factory);
baseResolveDirective(tView, lView, def, def.factory);
saveNameToExportMap(tView.data !.length - 1, def, exportsMap);
@ -1766,8 +1764,8 @@ export function checkView<T>(hostView: LView, component: T) {
function executeViewQueryFn<T>(flags: RenderFlags, tView: TView, component: T): void {
const viewQuery = tView.viewQuery;
if (viewQuery) {
setCurrentQueryIndex(tView.viewQueryStartIndex);
if (viewQuery !== null) {
setCurrentQueryIndex(0);
viewQuery(flags, component);
}
}

View File

@ -9,9 +9,9 @@
import {ViewRef} from '../../linker/view_ref';
import {TNode} from './node';
import {LQueries} from './query';
import {RComment, RElement} from './renderer';
import {HOST, LView, NEXT, PARENT, QUERIES, T_HOST} from './view';
import {HOST, LView, NEXT, PARENT, T_HOST} from './view';
/**
@ -26,8 +26,15 @@ export const TYPE = 1;
* Uglify will inline these when minifying so there shouldn't be a cost.
*/
export const ACTIVE_INDEX = 2;
// PARENT, NEXT, QUERIES and T_HOST are indices 3, 4, 5 and 6.
// PARENT and NEXT are indices 3 and 4
// As we already have these constants in LView, we don't need to re-create them.
export const MOVED_VIEWS = 5;
// T_HOST is index 6
// We already have this constants in LView, we don't need to re-create it.
export const NATIVE = 7;
export const VIEW_REFS = 8;
@ -84,11 +91,11 @@ export interface LContainer extends Array<any> {
[NEXT]: LView|LContainer|null;
/**
* Queries active for this container - all the views inserted to / removed from
* this container are reported to queries referenced here.
* A collection of views created based on the underlying `<ng-template>` element but inserted into
* a different `LContainer`. We need to track views created from a given declaration point since
* queries collect matches from the embedded view declaration point and _not_ the insertion point.
*/
[QUERIES]: LQueries|null; // TODO(misko): This is abuse of `LContainer` since we are storing
// `[QUERIES]` in it which are not needed for `LContainer` (only needed for Template)
[MOVED_VIEWS]: LView[]|null;
/**
* Pointer to the `TNode` which represents the host of the container.

View File

@ -9,99 +9,211 @@
import {Type} from '../../interface/type';
import {QueryList} from '../../linker';
import {TContainerNode, TElementContainerNode, TElementNode, TNode} from './node';
import {TNode} from './node';
import {TView} from './view';
/**
* An object representing query metadata extracted from query annotations.
*/
export interface TQueryMetadata {
predicate: Type<any>|string[];
descendants: boolean;
read: any;
isStatic: boolean;
}
/** Used for tracking queries (e.g. ViewChild, ContentChild). */
/**
* TQuery objects represent all the query-related data that remain the same from one view instance
* to another and can be determined on the very first template pass. Most notably TQuery holds all
* the matches for a given view.
*/
export interface TQuery {
/**
* Query metadata extracted from query annotations.
*/
metadata: TQueryMetadata;
/**
* Index of a query in a declaration view in case of queries propagated to en embedded view, -1
* for queries declared in a given view. We are storing this index so we can find a parent query
* to clone for an embedded view (when an embedded view is created).
*/
indexInDeclarationView: number;
/**
* Matches collected on the first template pass. Each match is a pair of:
* - TNode index;
* - match index;
*
* A TNode index can be either:
* - a positive number (the most common case) to indicate a matching TNode;
* - a negative number to indicate that a given query is crossing a <ng-template> element and
* results from views created based on TemplateRef should be inserted at this place.
*
* A match index is a number used to find an actual value (for a given node) when query results
* are materialized. This index can have one of the following values:
* - -2 - indicates that we need to read a special token (TemplateRef, ViewContainerRef etc.);
* - -1 - indicates that we need to read a default value based on the node type (TemplateRef for
* ng-template and ElementRef for other elements);
* - a positive number - index of an injectable to be read from the element injector.
*/
matches: number[]|null;
/**
* A flag indicating if a given query crosses an <ng-template> element. This flag exists for
* performance reasons: we can notice that queries not crossing any <ng-template> elements will
* have matches from a given view only (and adapt processing accordingly).
*/
crossesNgTemplate: boolean;
/**
* A method call when a given query is crossing an element (or element container). This is where a
* given TNode is matched against a query predicate.
* @param tView
* @param tNode
*/
elementStart(tView: TView, tNode: TNode): void;
/**
* A method called when processing the elementEnd instruction - this is mostly useful to determine
* if a given content query should match any nodes past this point.
* @param tNode
*/
elementEnd(tNode: TNode): void;
/**
* A method called when processing the template instruction. This is where a
* given TContainerNode is matched against a query predicate.
* @param tView
* @param tNode
*/
template(tView: TView, tNode: TNode): void;
/**
* A query-related method called when an embedded TView is created based on the content of a
* <ng-template> element. We call this method to determine if a given query should be propagated
* to the embedded view and if so - return a cloned TQuery for this embedded view.
* @param tNode
* @param childQueryIndex
*/
embeddedTView(tNode: TNode, childQueryIndex: number): TQuery|null;
}
/**
* TQueries represent a collection of individual TQuery objects tracked in a given view. Most of the
* methods on this interface are simple proxy methods to the corresponding functionality on TQuery.
*/
export interface TQueries {
/**
* Adds a new TQuery to a collection of queries tracked in a given view.
* @param tQuery
*/
track(tQuery: TQuery): void;
/**
* Returns a TQuery instance for at the given index in the queries array.
* @param index
*/
getByIndex(index: number): TQuery;
/**
* Returns the number of queries tracked in a given view.
*/
length: number;
/**
* A proxy method that iterates over all the TQueries in a given TView and calls the corresponding
* `elementStart` on each and every TQuery.
* @param tView
* @param tNode
*/
elementStart(tView: TView, tNode: TNode): void;
/**
* A proxy method that iterates over all the TQueries in a given TView and calls the corresponding
* `elementEnd` on each and every TQuery.
* @param tNode
*/
elementEnd(tNode: TNode): void;
/**
* A proxy method that iterates over all the TQueries in a given TView and calls the corresponding
* `template` on each and every TQuery.
* @param tView
* @param tNode
*/
template(tView: TView, tNode: TNode): void;
/**
* A proxy method that iterates over all the TQueries in a given TView and calls the corresponding
* `embeddedTView` on each and every TQuery.
* @param tNode
*/
embeddedTView(tNode: TNode): TQueries|null;
}
/**
* An interface that represents query-related information specific to a view instance. Most notably
* it contains:
* - materialized query matches;
* - a pointer to a QueryList where materialized query results should be reported.
*/
export interface LQuery<T> {
/**
* Materialized query matches for a given view only (!). Results are initialized lazily so the
* array of matches is set to `null` initially.
*/
matches: (T|null)[]|null;
/**
* A QueryList where materialized query results should be reported.
*/
queryList: QueryList<T>;
/**
* Clones an LQuery for an embedded view. A cloned query shares the same `QueryList` but has a
* separate collection of materialized matches.
*/
clone(): LQuery<T>;
/**
* Called when an embedded view, impacting results of this query, is inserted or removed.
*/
setDirty(): void;
}
/**
* lQueries represent a collection of individual LQuery objects tracked in a given view.
*/
export interface LQueries {
/**
* The parent LQueries instance.
*
* When there is a content query, a new LQueries instance is created to avoid mutating any
* existing LQueries. After we are done searching content children, the parent property allows
* us to traverse back up to the original LQueries instance to continue to search for matches
* in the main view.
* A collection of queries tracked in a given view.
*/
parent: LQueries|null;
queries: LQuery<any>[];
/**
* The index of the node on which this LQueries instance was created / cloned in a given LView.
*
* This index is stored to minimize LQueries cloning: we can observe that LQueries can be mutated
* only under 2 conditions:
* - we are crossing an element that has directives with content queries (new queries are added);
* - we are descending into element hierarchy (creating a child element of an existing element)
* and the current LQueries object is tracking shallow queries (shallow queries are removed).
*
* Since LQueries are not cloned systematically we need to know exactly where (on each element)
* cloning occurred, so we can properly restore the set of tracked queries when going up the
* elements hierarchy.
*
* Always set to -1 for view queries as view queries are created before we process any node in a
* given view.
* A method called when a new embedded view is created. As a result a set of LQueries applicable
* for a new embedded view is instantiated (cloned) from the declaration view.
* @param tView
*/
nodeIndex: number;
createEmbeddedView(tView: TView): LQueries|null;
/**
* Ask queries to prepare a copy of itself. This ensures that:
* - tracking new queries on content nodes doesn't mutate list of queries tracked on a parent
* node;
* - we don't track shallow queries when descending into elements hierarchy.
*
* We will clone LQueries before constructing content queries
* A method called when an embedded view is inserted into a container. As a result all impacted
* `LQuery` objects (and associated `QueryList`) are marked as dirty.
* @param tView
*/
clone(tNode: TNode): LQueries;
insertView(tView: TView): void;
/**
* Notify `LQueries` that a new `TNode` has been created and needs to be added to query results
* if matching query predicate.
* A method called when an embedded view is detached from a container. As a result all impacted
* `LQuery` objects (and associated `QueryList`) are marked as dirty.
* @param tView
*/
addNode(tNode: TElementNode|TContainerNode|TElementContainerNode): void;
/**
* Notify `LQueries` that a new `TNode` has been created and needs to be added to query results
* if matching query predicate. This is a special mode invoked if the query container has to
* be created out of order (e.g. view created in the constructor of a directive).
*/
insertNodeBeforeViews(tNode: TElementNode|TContainerNode|TElementContainerNode): void;
/**
* Notify `LQueries` that a new LContainer was added to ivy data structures. As a result we need
* to prepare room for views that might be inserted into this container.
*/
container(): LQueries|null;
/**
* Notify `LQueries` that a new `LView` has been created. As a result we need to prepare room
* and collect nodes that match query predicate.
*/
createView(): LQueries|null;
/**
* Notify `LQueries` that a new `LView` has been added to `LContainer`. As a result all
* the matching nodes from this view should be added to container's queries.
*/
insertView(newViewIndex: number): void;
/**
* Notify `LQueries` that an `LView` has been removed from `LContainer`. As a result all
* the matching nodes from this view should be removed from container's queries.
*/
removeView(): void;
/**
* Add additional `QueryList` to track.
*
* @param queryList `QueryList` to update with changes.
* @param predicate Either `Type` or selector array of [key, value] predicates.
* @param descend If true the query will recursively apply to the children.
* @param read Indicates which token should be read from DI for this query.
*/
track<T>(
queryList: QueryList<T>, predicate: Type<any>|string[], descend?: boolean,
read?: Type<T>): void;
detachView(tView: TView): void;
}
// Note: This hack is necessary so we don't erroneously get a circular dependency
// failure based on types.
export const unusedValueExportToPlacateAjd = 1;

View File

@ -9,7 +9,6 @@
import {InjectionToken} from '../../di/injection_token';
import {Injector} from '../../di/injector';
import {Type} from '../../interface/type';
import {QueryList} from '../../linker';
import {SchemaMetadata} from '../../metadata';
import {Sanitizer} from '../../sanitization/security';
@ -18,7 +17,7 @@ import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefList, HostBin
import {I18nUpdateOpCodes, TI18n} from './i18n';
import {TElementNode, TNode, TViewNode} from './node';
import {PlayerHandler} from './player';
import {LQueries} from './query';
import {LQueries, TQueries} from './query';
import {RElement, Renderer3, RendererFactory3} from './renderer';
@ -42,11 +41,11 @@ export const RENDERER = 12;
export const SANITIZER = 13;
export const CHILD_HEAD = 14;
export const CHILD_TAIL = 15;
export const CONTENT_QUERIES = 16;
export const DECLARATION_VIEW = 17;
export const DECLARATION_VIEW = 16;
export const DECLARATION_LCONTAINER = 17;
export const PREORDER_HOOK_FLAGS = 18;
/** Size of LView's header. Necessary to adjust for it when setting slots. */
export const HEADER_OFFSET = 20;
export const HEADER_OFFSET = 19;
// This interface replaces the real LView interface if it is an arg or a
@ -179,13 +178,6 @@ export interface LView extends Array<any> {
*/
[CHILD_TAIL]: LView|LContainer|null;
/**
* Stores QueryLists associated with content queries of a directive. This data structure is
* filled-in as part of a directive creation process and is later used to retrieve a QueryList to
* be refreshed.
*/
[CONTENT_QUERIES]: QueryList<any>[]|null;
/**
* View where this view's template was declared.
*
@ -212,6 +204,16 @@ export interface LView extends Array<any> {
*/
[DECLARATION_VIEW]: LView|null;
/**
* A declaration point of embedded views (ones instantiated based on the content of a
* <ng-template>), null for other types of views.
*
* We need to track all embedded views created from a given declaration point so we can prepare
* query matches in a proper order (query matches are ordered based on their declaration point and
* _not_ the insertion point).
*/
[DECLARATION_LCONTAINER]: LContainer|null;
/**
* More flags for this view. See PreOrderHookFlags for more info.
*/
@ -410,17 +412,6 @@ export interface TView {
*/
staticContentQueries: boolean;
/**
* The index where the viewQueries section of `LView` begins. This section contains
* view queries defined for a component/directive.
*
* We store this start index so we know where the list of view queries starts.
* This is required when we invoke view queries at runtime. We invoke queries one by one and
* increment query index after each iteration. This information helps us to reset index back to
* the beginning of view query list before we invoke view queries again.
*/
viewQueryStartIndex: number;
/**
* A reference to the first child node located in the view.
*/
@ -550,7 +541,19 @@ export interface TView {
components: number[]|null;
/**
* A list of indices for child directives that have content queries.
* A collection of queries tracked in a given view.
*/
queries: TQueries|null;
/**
* An array of indices pointing to directives with content queries alongside with the
* corresponding
* query index. Each entry in this array is a tuple of:
* - index of the first content query index declared by a given directive;
* - index of a directive.
*
* We are storing those indexes so we can refresh content queries as part of a view refresh
* process.
*/
contentQueries: number[]|null;

View File

@ -11,18 +11,18 @@ import {assertDefined, assertDomNode} from '../util/assert';
import {assertLContainer, assertLView} from './assert';
import {attachPatchData} from './context_discovery';
import {CONTAINER_HEADER_OFFSET, LContainer, NATIVE, unusedValueExportToPlacateAjd as unused1} from './interfaces/container';
import {CONTAINER_HEADER_OFFSET, LContainer, MOVED_VIEWS, NATIVE, unusedValueExportToPlacateAjd as unused1} from './interfaces/container';
import {ComponentDef} from './interfaces/definition';
import {NodeInjectorFactory} from './interfaces/injector';
import {TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeType, TProjectionNode, TViewNode, unusedValueExportToPlacateAjd as unused2} from './interfaces/node';
import {unusedValueExportToPlacateAjd as unused3} from './interfaces/projection';
import {ProceduralRenderer3, RElement, RNode, RText, Renderer3, isProceduralRenderer, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer';
import {isLContainer, isLView, isRootView} from './interfaces/type_checks';
import {CHILD_HEAD, CLEANUP, FLAGS, HOST, HookData, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, TVIEW, T_HOST, unusedValueExportToPlacateAjd as unused5} from './interfaces/view';
import {CHILD_HEAD, CLEANUP, DECLARATION_LCONTAINER, FLAGS, HOST, HookData, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, TVIEW, T_HOST, unusedValueExportToPlacateAjd as unused5} from './interfaces/view';
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
import {renderStringify} from './util/misc_utils';
import {findComponentView, getLViewParent} from './util/view_traversal_utils';
import {getNativeByTNode, getNativeByTNodeOrNull, unwrapRNode, viewAttachedToContainer} from './util/view_utils';
import {getNativeByTNode, getNativeByTNodeOrNull, unwrapRNode} from './util/view_utils';
const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4 + unused5;
@ -217,15 +217,46 @@ export function insertView(lView: LView, lContainer: LContainer, index: number)
lView[PARENT] = lContainer;
// Notify query that a new view has been added
if (lView[QUERIES]) {
lView[QUERIES] !.insertView(index);
// track views where declaration and insertion points are different
const declarationLContainer = lView[DECLARATION_LCONTAINER];
if (declarationLContainer !== null && lContainer !== declarationLContainer) {
trackMovedView(declarationLContainer, lView);
}
// notify query that a new view has been added
const lQueries = lView[QUERIES];
if (lQueries !== null) {
lQueries.insertView(lView[TVIEW]);
}
// Sets the attached flag
lView[FLAGS] |= LViewFlags.Attached;
}
/**
* Track views created from the declaration container (TemplateRef) and inserted into a
* different LContainer.
*/
function trackMovedView(declarationContainer: LContainer, lView: LView) {
ngDevMode && assertLContainer(declarationContainer);
const declaredViews = declarationContainer[MOVED_VIEWS];
if (declaredViews === null) {
declarationContainer[MOVED_VIEWS] = [lView];
} else {
declaredViews.push(lView);
}
}
function detachMovedView(declarationContainer: LContainer, lView: LView) {
ngDevMode && assertLContainer(declarationContainer);
ngDevMode && assertDefined(
declarationContainer[MOVED_VIEWS],
'A projected view should belong to a non-empty projected views collection');
const projectedViews = declarationContainer[MOVED_VIEWS] !;
const declaredViewIndex = projectedViews.indexOf(lView);
projectedViews.splice(declaredViewIndex, 1);
}
/**
* Detaches a view from a container.
*
@ -241,17 +272,26 @@ export function detachView(lContainer: LContainer, removeIndex: number): LView|u
const indexInContainer = CONTAINER_HEADER_OFFSET + removeIndex;
const viewToDetach = lContainer[indexInContainer];
if (viewToDetach) {
const declarationLContainer = viewToDetach[DECLARATION_LCONTAINER];
if (declarationLContainer !== null && declarationLContainer !== lContainer) {
detachMovedView(declarationLContainer, viewToDetach);
}
if (removeIndex > 0) {
lContainer[indexInContainer - 1][NEXT] = viewToDetach[NEXT] as LView;
}
lContainer.splice(CONTAINER_HEADER_OFFSET + removeIndex, 1);
const removedLView = lContainer.splice(CONTAINER_HEADER_OFFSET + removeIndex, 1)[0];
addRemoveViewFromContainer(viewToDetach, false);
if ((viewToDetach[FLAGS] & LViewFlags.Attached) &&
!(viewToDetach[FLAGS] & LViewFlags.Destroyed) && viewToDetach[QUERIES]) {
viewToDetach[QUERIES] !.removeView();
// notify query that a view has been removed
const lQueries = removedLView[QUERIES];
if (lQueries !== null) {
lQueries.detachView(removedLView[TVIEW]);
}
viewToDetach[PARENT] = null;
viewToDetach[NEXT] = null;
// Unsets the attached flag
@ -342,9 +382,20 @@ function cleanUpView(view: LView | LContainer): void {
ngDevMode && ngDevMode.rendererDestroy++;
(view[RENDERER] as ProceduralRenderer3).destroy();
}
// For embedded views still attached to a container: remove query result from this view.
if (viewAttachedToContainer(view) && view[QUERIES]) {
view[QUERIES] !.removeView();
const declarationContainer = view[DECLARATION_LCONTAINER];
// we are dealing with an embedded view that is still inserted into a container
if (declarationContainer !== null && isLContainer(view[PARENT])) {
// and this is a projected view
if (declarationContainer !== view[PARENT]) {
detachMovedView(declarationContainer, view);
}
// For embedded views still attached to a container: remove query result from this view.
const lQueries = view[QUERIES];
if (lQueries !== null) {
lQueries.detachView(view[TVIEW]);
}
}
}
}
@ -878,16 +929,18 @@ function executeActionOnNode(
renderer: Renderer3, action: WalkTNodeTreeAction, lView: LView, tNode: TNode,
renderParent: RElement | null, beforeNode: RNode | null | undefined): void {
const nodeType = tNode.type;
if (nodeType === TNodeType.ElementContainer || nodeType === TNodeType.IcuContainer) {
executeActionOnElementContainerOrIcuContainer(
renderer, action, lView, tNode as TElementContainerNode | TIcuContainerNode, renderParent,
beforeNode);
} else if (nodeType === TNodeType.Projection) {
executeActionOnProjection(
renderer, action, lView, tNode as TProjectionNode, renderParent, beforeNode);
} else {
ngDevMode && assertNodeOfPossibleTypes(tNode, TNodeType.Element, TNodeType.Container);
executeActionOnElementOrContainer(
action, renderer, renderParent, lView[tNode.index], beforeNode);
if (!(tNode.flags & TNodeFlags.isDetached)) {
if (nodeType === TNodeType.ElementContainer || nodeType === TNodeType.IcuContainer) {
executeActionOnElementContainerOrIcuContainer(
renderer, action, lView, tNode as TElementContainerNode | TIcuContainerNode, renderParent,
beforeNode);
} else if (nodeType === TNodeType.Projection) {
executeActionOnProjection(
renderer, action, lView, tNode as TProjectionNode, renderParent, beforeNode);
} else {
ngDevMode && assertNodeOfPossibleTypes(tNode, TNodeType.Element, TNodeType.Container);
executeActionOnElementOrContainer(
action, renderer, renderParent, lView[tNode.index], beforeNode);
}
}
}

View File

@ -13,195 +13,240 @@ import {Type} from '../interface/type';
import {ElementRef as ViewEngine_ElementRef} from '../linker/element_ref';
import {QueryList} from '../linker/query_list';
import {TemplateRef as ViewEngine_TemplateRef} from '../linker/template_ref';
import {assertDataInRange, assertDefined, assertEqual} from '../util/assert';
import {ViewContainerRef} from '../linker/view_container_ref';
import {assertDataInRange, assertDefined, throwError} from '../util/assert';
import {stringify} from '../util/stringify';
import {assertPreviousIsParent} from './assert';
import {assertFirstTemplatePass, assertLContainer} from './assert';
import {getNodeInjectable, locateDirectiveOrProvider} from './di';
import {NG_ELEMENT_ID} from './fields';
import {store} from './instructions/all';
import {storeCleanupWithContext} from './instructions/shared';
import {CONTAINER_HEADER_OFFSET, LContainer, MOVED_VIEWS} from './interfaces/container';
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 {isContentQueryHost} from './interfaces/type_checks';
import {CONTENT_QUERIES, HEADER_OFFSET, LView, QUERIES, TVIEW, TView} from './interfaces/view';
import {getCurrentQueryIndex, getIsParent, getLView, getPreviousOrParentTNode, isCreationMode, setCurrentQueryIndex} from './state';
import {loadInternal} from './util/view_utils';
import {createElementRef, createTemplateRef} from './view_engine_compatibility';
import {LQueries, LQuery, TQueries, TQuery, TQueryMetadata, unusedValueExportToPlacateAjd as unused4} from './interfaces/query';
import {DECLARATION_LCONTAINER, LView, PARENT, QUERIES, TVIEW, TView} from './interfaces/view';
import {assertNodeOfPossibleTypes} from './node_assert';
import {getCurrentQueryIndex, getLView, getPreviousOrParentTNode, isCreationMode, setCurrentQueryIndex} from './state';
import {createContainerRef, createElementRef, createTemplateRef} from './view_engine_compatibility';
const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4;
/**
* A predicate which determines if a given element/directive should be included in the query
* results.
*/
export interface QueryPredicate<T> {
/**
* If looking for directives then it contains the directive type.
*/
type: Type<T>|null;
/**
* If selector then contains local names to query for.
*/
selector: string[]|null;
/**
* Indicates which token should be read from DI for this query.
*/
read: Type<T>|null;
class LQuery_<T> implements LQuery<T> {
matches: (T|null)[]|null = null;
constructor(public queryList: QueryList<T>) {}
clone(): LQuery<T> { return new LQuery_(this.queryList); }
setDirty(): void { this.queryList.setDirty(); }
}
/**
* An object representing a query, which is a combination of:
* - query predicate to determines if a given element/directive should be included in the query
* - values collected based on a predicate
* - `QueryList` to which collected values should be reported
*/
class LQuery<T> {
constructor(
/**
* Next query. Used when queries are stored as a linked list in `LQueries`.
*/
public next: LQuery<any>|null,
class LQueries_ implements LQueries {
constructor(public queries: LQuery<any>[] = []) {}
/**
* Destination to which the value should be added.
*/
public list: QueryList<T>,
createEmbeddedView(tView: TView): LQueries|null {
const tQueries = tView.queries;
if (tQueries !== null) {
const noOfInheritedQueries =
tView.contentQueries !== null ? tView.contentQueries[0] : tQueries.length;
const viewLQueries: LQuery<any>[] = new Array(noOfInheritedQueries);
/**
* A predicate which determines if a given element/directive should be included in the query
* results.
*/
public predicate: QueryPredicate<T>,
// An embedded view has queries propagated from a declaration view at the beginning of the
// TQueries collection and up until a first content query declared in the embedded view. Only
// propagated LQueries are created at this point (LQuery corresponding to declared content
// queries will be instantiated from the content query instructions for each directive).
for (let i = 0; i < noOfInheritedQueries; i++) {
const tQuery = tQueries.getByIndex(i);
const parentLQuery = this.queries[tQuery.indexInDeclarationView];
viewLQueries[i] = parentLQuery.clone();
}
/**
* Values which have been located.
* This is what builds up the `QueryList._valuesTree`.
*/
public values: any[],
return new LQueries_(viewLQueries);
}
/**
* A pointer to an array that stores collected values from views. This is necessary so we
* know a container into which to insert nodes collected from views.
*/
public containerValues: any[]|null) {}
return null;
}
insertView(tView: TView): void { this.dirtyQueriesWithMatches(tView); }
detachView(tView: TView): void { this.dirtyQueriesWithMatches(tView); }
private dirtyQueriesWithMatches(tView: TView) {
for (let i = 0; i < this.queries.length; i++) {
if (getTQuery(tView, i).matches !== null) {
this.queries[i].setDirty();
}
}
}
}
export class LQueries_ implements LQueries {
class TQueryMetadata_ implements TQueryMetadata {
constructor(
public parent: LQueries_|null, private shallow: LQuery<any>|null,
private deep: LQuery<any>|null, public nodeIndex: number = -1) {}
public predicate: Type<any>|string[], public descendants: boolean, public read: any,
public isStatic: boolean) {}
}
track<T>(queryList: QueryList<T>, predicate: Type<T>|string[], descend?: boolean, read?: Type<T>):
void {
if (descend) {
this.deep = createLQuery(this.deep, queryList, predicate, read != null ? read : null);
class TQueries_ implements TQueries {
constructor(private queries: TQuery[] = []) {}
elementStart(tView: TView, tNode: TNode): void {
ngDevMode && assertFirstTemplatePass(
tView, 'Queries should collect results on the first template pass only');
for (let query of this.queries) {
query.elementStart(tView, tNode);
}
}
elementEnd(tNode: TNode): void {
for (let query of this.queries) {
query.elementEnd(tNode);
}
}
embeddedTView(tNode: TNode): TQueries|null {
let queriesForTemplateRef: TQuery[]|null = null;
for (let i = 0; i < this.length; i++) {
const childQueryIndex = queriesForTemplateRef !== null ? queriesForTemplateRef.length : 0;
const tqueryClone = this.getByIndex(i).embeddedTView(tNode, childQueryIndex);
if (tqueryClone) {
tqueryClone.indexInDeclarationView = i;
if (queriesForTemplateRef !== null) {
queriesForTemplateRef.push(tqueryClone);
} else {
queriesForTemplateRef = [tqueryClone];
}
}
}
return queriesForTemplateRef !== null ? new TQueries_(queriesForTemplateRef) : null;
}
template(tView: TView, tNode: TNode): void {
ngDevMode && assertFirstTemplatePass(
tView, 'Queries should collect results on the first template pass only');
for (let query of this.queries) {
query.template(tView, tNode);
}
}
getByIndex(index: number): TQuery {
ngDevMode && assertDataInRange(this.queries, index);
return this.queries[index];
}
get length(): number { return this.queries.length; }
track(tquery: TQuery): void { this.queries.push(tquery); }
}
class TQuery_ implements TQuery {
matches: number[]|null = null;
indexInDeclarationView = -1;
crossesNgTemplate = false;
/**
* A node index on which a query was declared (-1 for view queries and ones inherited from the
* declaration template). We use this index (alongside with _appliesToNextNode flag) to know
* when to apply content queries to elements in a template.
*/
private _declarationNodeIndex: number;
/**
* A flag indicating if a given query still applies to nodes it is crossing. We use this flag
* (alongside with _declarationNodeIndex) to know when to stop applying content queries to
* elements in a template.
*/
private _appliesToNextNode = true;
constructor(public metadata: TQueryMetadata, nodeIndex: number = -1) {
this._declarationNodeIndex = nodeIndex;
}
elementStart(tView: TView, tNode: TNode): void {
if (this.isApplyingToNode(tNode)) {
this.matchTNode(tView, tNode);
}
}
elementEnd(tNode: TNode): void {
if (this._declarationNodeIndex === tNode.index) {
this._appliesToNextNode = false;
}
}
template(tView: TView, tNode: TNode): void { this.elementStart(tView, tNode); }
embeddedTView(tNode: TNode, childQueryIndex: number): TQuery|null {
if (this.isApplyingToNode(tNode)) {
this.crossesNgTemplate = true;
// A marker indicating a `<ng-template>` element (a placeholder for query results from
// embedded views created based on this `<ng-template>`).
this.addMatch(-tNode.index, childQueryIndex);
return new TQuery_(this.metadata);
}
return null;
}
private isApplyingToNode(tNode: TNode): boolean {
if (this._appliesToNextNode && this.metadata.descendants === false) {
return this._declarationNodeIndex === (tNode.parent ? tNode.parent.index : -1);
}
return this._appliesToNextNode;
}
private matchTNode(tView: TView, tNode: TNode): void {
if (Array.isArray(this.metadata.predicate)) {
this.matchTNodeByLocalNames(tView, tNode, this.metadata.predicate as string[]);
} else {
this.shallow = createLQuery(this.shallow, queryList, predicate, read != null ? read : null);
const typePredicate = this.metadata.predicate as any;
if (typePredicate === ViewEngine_TemplateRef) {
this.matchTNodeByTemplateRef(tView, tNode);
} else {
this.matchTNodeByType(tView, tNode, typePredicate);
}
}
}
clone(tNode: TNode): LQueries {
return this.shallow !== null || isContentQueryHost(tNode) ?
new LQueries_(this, null, this.deep, tNode.index) :
this;
}
container(): LQueries|null {
const shallowResults = copyQueriesToContainer(this.shallow);
const deepResults = copyQueriesToContainer(this.deep);
return shallowResults || deepResults ? new LQueries_(this, shallowResults, deepResults) : null;
}
createView(): LQueries|null {
const shallowResults = copyQueriesToView(this.shallow);
const deepResults = copyQueriesToView(this.deep);
return shallowResults || deepResults ? new LQueries_(this, shallowResults, deepResults) : null;
}
insertView(index: number): void {
insertView(index, this.shallow);
insertView(index, this.deep);
}
addNode(tNode: TElementNode|TContainerNode|TElementContainerNode): void {
add(this.deep, tNode, false);
add(this.shallow, tNode, false);
}
insertNodeBeforeViews(tNode: TElementNode|TContainerNode|TElementContainerNode): void {
add(this.deep, tNode, true);
add(this.shallow, tNode, true);
}
removeView(): void {
removeView(this.shallow);
removeView(this.deep);
}
}
function copyQueriesToContainer(query: LQuery<any>| null): LQuery<any>|null {
let result: LQuery<any>|null = null;
while (query) {
const containerValues: any[] = []; // prepare room for views
query.values.push(containerValues);
result = new LQuery<any>(result, query.list, query.predicate, containerValues, null);
query = query.next;
}
return result;
}
function copyQueriesToView(query: LQuery<any>| null): LQuery<any>|null {
let result: LQuery<any>|null = null;
while (query) {
result = new LQuery<any>(result, query.list, query.predicate, [], query.values);
query = query.next;
}
return result;
}
function insertView(index: number, query: LQuery<any>| null) {
while (query) {
ngDevMode && assertViewQueryhasPointerToDeclarationContainer(query);
query.containerValues !.splice(index, 0, query.values);
// mark a query as dirty only when inserted view had matching modes
if (query.values.length) {
query.list.setDirty();
private matchTNodeByLocalNames(tView: TView, tNode: TNode, localNames: string[]): void {
for (let i = 0; i < localNames.length; i++) {
this.matchTNodeWithReadOption(tView, tNode, getIdxOfMatchingSelector(tNode, localNames[i]));
}
query = query.next;
}
}
function removeView(query: LQuery<any>| null) {
while (query) {
ngDevMode && assertViewQueryhasPointerToDeclarationContainer(query);
private matchTNodeByTemplateRef(tView: TView, tNode: TNode): void {
this.matchTNodeWithReadOption(tView, tNode, tNode.type === TNodeType.Container ? -1 : null);
}
const containerValues = query.containerValues !;
const viewValuesIdx = containerValues.indexOf(query.values);
const removed = containerValues.splice(viewValuesIdx, 1);
private matchTNodeByType(tView: TView, tNode: TNode, typePredicate: Type<any>): void {
this.matchTNodeWithReadOption(
tView, tNode, locateDirectiveOrProvider(tNode, tView, typePredicate, false, false));
}
// mark a query as dirty only when removed view had matching modes
ngDevMode && assertEqual(removed.length, 1, 'removed.length');
if (removed[0].length) {
query.list.setDirty();
private matchTNodeWithReadOption(tView: TView, tNode: TNode, nodeMatchIdx: number|null): void {
if (nodeMatchIdx !== null) {
const read = this.metadata.read;
if (read !== null) {
if (read === ViewEngine_ElementRef || read === ViewContainerRef ||
read === ViewEngine_TemplateRef && tNode.type === TNodeType.Container) {
this.addMatch(tNode.index, -2);
} else {
const directiveOrProviderIdx =
locateDirectiveOrProvider(tNode, tView, read, false, false);
if (directiveOrProviderIdx !== null) {
this.addMatch(tNode.index, directiveOrProviderIdx);
}
}
} else {
this.addMatch(tNode.index, nodeMatchIdx);
}
}
query = query.next;
}
}
function assertViewQueryhasPointerToDeclarationContainer(query: LQuery<any>) {
assertDefined(query.containerValues, 'View queries need to have a pointer to container values.');
private addMatch(tNodeIdx: number, matchIdx: number) {
if (this.matches === null) {
this.matches = [tNodeIdx, matchIdx];
} else {
this.matches.push(tNodeIdx, matchIdx);
}
}
}
/**
@ -214,7 +259,7 @@ function assertViewQueryhasPointerToDeclarationContainer(query: LQuery<any>) {
*/
function getIdxOfMatchingSelector(tNode: TNode, selector: string): number|null {
const localNames = tNode.localNames;
if (localNames) {
if (localNames !== null) {
for (let i = 0; i < localNames.length; i += 2) {
if (localNames[i] === selector) {
return localNames[i + 1] as number;
@ -225,148 +270,127 @@ function getIdxOfMatchingSelector(tNode: TNode, selector: string): number|null {
}
// TODO: "read" should be an AbstractType (FW-486)
function queryByReadToken(read: any, tNode: TNode, currentView: LView): any {
const factoryFn = (read as any)[NG_ELEMENT_ID];
if (typeof factoryFn === 'function') {
return factoryFn();
} else {
const tView = currentView[TVIEW];
const matchingIdx = locateDirectiveOrProvider(tNode, tView, read as Type<any>, false, false);
if (matchingIdx !== null) {
return getNodeInjectable(tView.data, currentView, matchingIdx, tNode as TElementNode);
}
}
return null;
}
function queryByTNodeType(tNode: TNode, currentView: LView): any {
function createResultByTNodeType(tNode: TNode, currentView: LView): any {
if (tNode.type === TNodeType.Element || tNode.type === TNodeType.ElementContainer) {
return createElementRef(ViewEngine_ElementRef, tNode, currentView);
}
if (tNode.type === TNodeType.Container) {
} else if (tNode.type === TNodeType.Container) {
return createTemplateRef(ViewEngine_TemplateRef, ViewEngine_ElementRef, tNode, currentView);
}
return null;
}
function queryByTemplateRef(
templateRefToken: ViewEngine_TemplateRef<any>, tNode: TNode, currentView: LView,
read: any): any {
const templateRefResult = (templateRefToken as any)[NG_ELEMENT_ID]();
if (read) {
return templateRefResult ? queryByReadToken(read, tNode, currentView) : null;
function createResultForNode(lView: LView, tNode: TNode, matchingIdx: number, read: any): any {
if (matchingIdx === -1) {
// if read token and / or strategy is not specified, detect it using appropriate tNode type
return createResultByTNodeType(tNode, lView);
} else if (matchingIdx === -2) {
// read a special token from a node injector
return createSpecialToken(lView, tNode, read);
} else {
// read a token
return getNodeInjectable(lView[TVIEW].data, lView, matchingIdx, tNode as TElementNode);
}
return templateRefResult;
}
function queryRead(tNode: TNode, currentView: LView, read: any, matchingIdx: number): any {
if (read) {
return queryByReadToken(read, tNode, currentView);
function createSpecialToken(lView: LView, tNode: TNode, read: any): any {
if (read === ViewEngine_ElementRef) {
return createElementRef(ViewEngine_ElementRef, tNode, lView);
} else if (read === ViewEngine_TemplateRef) {
return createTemplateRef(ViewEngine_TemplateRef, ViewEngine_ElementRef, tNode, lView);
} else if (read === ViewContainerRef) {
ngDevMode && assertNodeOfPossibleTypes(
tNode, TNodeType.Element, TNodeType.Container, TNodeType.ElementContainer);
return createContainerRef(
ViewContainerRef, ViewEngine_ElementRef,
tNode as TElementNode | TContainerNode | TElementContainerNode, lView);
} else {
ngDevMode &&
throwError(
`Special token to read should be one of ElementRef, TemplateRef or ViewContainerRef but got ${stringify(read)}.`);
}
if (matchingIdx > -1) {
return getNodeInjectable(
currentView[TVIEW].data, currentView, matchingIdx, tNode as TElementNode);
}
// if read token and / or strategy is not specified,
// detect it using appropriate tNode type
return queryByTNodeType(tNode, currentView);
}
/**
* Add query matches for a given node.
*
* @param query The first query in the linked list
* @param tNode The TNode to match against queries
* @param insertBeforeContainer Whether or not we should add matches before the last
* container array. This mode is necessary if the query container had to be created
* out of order (e.g. a view was created in a constructor)
* A helper function that creates query results for a given view. This function is meant to do the
* processing once and only once for a given view instance (a set of results for a given view
* doesn't change).
*/
function add(
query: LQuery<any>| null, tNode: TElementNode | TContainerNode | TElementContainerNode,
insertBeforeContainer: boolean) {
const lView = getLView();
const tView = lView[TVIEW];
while (query) {
const predicate = query.predicate;
const type = predicate.type as any;
if (type) {
let result = null;
if (type === ViewEngine_TemplateRef) {
result = queryByTemplateRef(type, tNode, lView, predicate.read);
function materializeViewResults<T>(lView: LView, tQuery: TQuery, queryIndex: number): (T | null)[] {
const lQuery = lView[QUERIES] !.queries ![queryIndex];
if (lQuery.matches === null) {
const tViewData = lView[TVIEW].data;
const tQueryMatches = tQuery.matches !;
const result: T|null[] = new Array(tQueryMatches.length / 2);
for (let i = 0; i < tQueryMatches.length; i += 2) {
const matchedNodeIdx = tQueryMatches[i];
if (matchedNodeIdx < 0) {
// we at the <ng-template> marker which might have results in views created based on this
// <ng-template> - those results will be in separate views though, so here we just leave
// null as a placeholder
result[i / 2] = null;
} else {
const matchingIdx = locateDirectiveOrProvider(tNode, tView, type, false, false);
if (matchingIdx !== null) {
result = queryRead(tNode, lView, predicate.read, matchingIdx);
ngDevMode && assertDataInRange(tViewData, matchedNodeIdx);
const tNode = tViewData[matchedNodeIdx] as TNode;
result[i / 2] =
createResultForNode(lView, tNode, tQueryMatches[i + 1], tQuery.metadata.read);
}
}
lQuery.matches = result;
}
return lQuery.matches;
}
/**
* A helper function that collects (already materialized) query results from a tree of views,
* starting with a provided LView.
*/
function collectQueryResults<T>(
lView: LView, tQuery: TQuery, queryIndex: number, result: T[]): T[] {
ngDevMode &&
assertDefined(
tQuery.matches, 'Query results can only be collected for queries with existing matches.');
const tQueryMatches = tQuery.matches !;
const lViewResults = materializeViewResults<T>(lView, tQuery, queryIndex);
for (let i = 0; i < tQueryMatches.length; i += 2) {
const tNodeIdx = tQueryMatches[i];
if (tNodeIdx > 0) {
const viewResult = lViewResults[i / 2];
ngDevMode && assertDefined(viewResult, 'materialized query result should be defined');
result.push(viewResult as T);
} else {
const childQueryIndex = tQueryMatches[i + 1];
const declarationLContainer = lView[-tNodeIdx] as LContainer;
ngDevMode && assertLContainer(declarationLContainer);
// collect matches for views inserted in this container
for (let i = CONTAINER_HEADER_OFFSET; i < declarationLContainer.length; i++) {
const embeddedLView = declarationLContainer[i];
if (embeddedLView[DECLARATION_LCONTAINER] === embeddedLView[PARENT]) {
const tquery = getTQuery(embeddedLView[TVIEW], childQueryIndex);
if (tquery.matches !== null) {
collectQueryResults(embeddedLView, tquery, childQueryIndex, result);
}
}
}
if (result !== null) {
addMatch(query, result, insertBeforeContainer);
}
} else {
const selector = predicate.selector !;
for (let i = 0; i < selector.length; i++) {
const matchingIdx = getIdxOfMatchingSelector(tNode, selector[i]);
if (matchingIdx !== null) {
const result = queryRead(tNode, lView, predicate.read, matchingIdx);
if (result !== null) {
addMatch(query, result, insertBeforeContainer);
// collect matches for views created from this declaration container and inserted into
// different containers
if (declarationLContainer[MOVED_VIEWS] !== null) {
for (let embeddedLView of declarationLContainer[MOVED_VIEWS] !) {
const tquery = getTQuery(embeddedLView[TVIEW], childQueryIndex);
if (tquery.matches !== null) {
collectQueryResults(embeddedLView, tquery, childQueryIndex, result);
}
}
}
}
query = query.next;
}
}
function addMatch(query: LQuery<any>, matchingValue: any, insertBeforeViewMatches: boolean): void {
// Views created in constructors may have their container values created too early. In this case,
// ensure template node results are unshifted before container results. Otherwise, results inside
// embedded views will appear before results on parent template nodes when flattened.
insertBeforeViewMatches ? query.values.unshift(matchingValue) : query.values.push(matchingValue);
query.list.setDirty();
}
function createPredicate<T>(predicate: Type<T>| string[], read: Type<T>| null): QueryPredicate<T> {
const isArray = Array.isArray(predicate);
return {
type: isArray ? null : predicate as Type<T>,
selector: isArray ? predicate as string[] : null,
read: read
};
}
function createLQuery<T>(
previous: LQuery<any>| null, queryList: QueryList<T>, predicate: Type<T>| string[],
read: Type<T>| null): LQuery<T> {
return new LQuery(
previous, queryList, createPredicate(predicate, read),
(queryList as any as QueryList_<T>)._valuesTree, null);
}
type QueryList_<T> = QueryList<T>& {_valuesTree: any[], _static: boolean};
/**
* Creates a QueryList and stores it in LView's collection of active queries (LQueries).
*
* @param predicate The type for which the query will search
* @param descend Whether or not to descend into children
* @param read What to save in the query
* @returns QueryList<T>
*/
function createQueryListInLView<T>(
// TODO: "read" should be an AbstractType (FW-486)
lView: LView, predicate: Type<any>| string[], descend: boolean, read: any, isStatic: boolean,
nodeIndex: number): QueryList<T> {
ngDevMode && assertPreviousIsParent(getIsParent());
const queryList = new QueryList<T>() as QueryList_<T>;
const queries = lView[QUERIES] || (lView[QUERIES] = new LQueries_(null, null, null, nodeIndex));
queryList._valuesTree = [];
queryList._static = isStatic;
queries.track(queryList, predicate, descend, read);
storeCleanupWithContext(lView, queryList, queryList.destroy);
return queryList;
return result;
}
/**
@ -379,15 +403,24 @@ function createQueryListInLView<T>(
* @codeGenApi
*/
export function ɵɵqueryRefresh(queryList: QueryList<any>): boolean {
const queryListImpl = (queryList as any as QueryList_<any>);
const creationMode = isCreationMode();
const lView = getLView();
const queryIndex = getCurrentQueryIndex();
// if creation mode and static or update mode and not static
if (queryList.dirty && creationMode === queryListImpl._static) {
queryList.reset(queryListImpl._valuesTree || []);
queryList.notifyOnChanges();
setCurrentQueryIndex(queryIndex + 1);
const tQuery = getTQuery(lView[TVIEW], queryIndex);
if (queryList.dirty && (isCreationMode() === tQuery.metadata.isStatic)) {
if (tQuery.matches === null) {
queryList.reset([]);
} else {
const result = tQuery.crossesNgTemplate ? collectQueryResults(lView, tQuery, queryIndex, []) :
materializeViewResults(lView, tQuery, queryIndex);
queryList.reset(result);
queryList.notifyOnChanges();
}
return true;
}
return false;
}
@ -401,12 +434,8 @@ export function ɵɵqueryRefresh(queryList: QueryList<any>): boolean {
* @codeGenApi
*/
export function ɵɵstaticViewQuery<T>(
// TODO(FW-486): "read" should be an AbstractType
predicate: Type<any>| string[], descend: boolean, read: any): void {
const lView = getLView();
const tView = lView[TVIEW];
viewQueryInternal(lView, tView, predicate, descend, read, true);
tView.staticViewQueries = true;
viewQueryInternal(getLView(), predicate, descend, read, true);
}
/**
@ -415,41 +444,33 @@ export function ɵɵstaticViewQuery<T>(
* @param predicate The type for which the query will search
* @param descend Whether or not to descend into children
* @param read What to save in the query
* @returns QueryList<T>
*
* @codeGenApi
*/
export function ɵɵviewQuery<T>(
// TODO(FW-486): "read" should be an AbstractType
predicate: Type<any>| string[], descend: boolean, read: any): QueryList<T> {
const lView = getLView();
const tView = lView[TVIEW];
return viewQueryInternal(lView, tView, predicate, descend, read, false);
export function ɵɵviewQuery<T>(predicate: Type<any>| string[], descend: boolean, read: any): void {
viewQueryInternal(getLView(), predicate, descend, read, false);
}
function viewQueryInternal<T>(
lView: LView, tView: TView, predicate: Type<any>| string[], descend: boolean, read: any,
isStatic: boolean): QueryList<T> {
lView: LView, predicate: Type<any>| string[], descend: boolean, read: any,
isStatic: boolean): void {
const tView = lView[TVIEW];
if (tView.firstTemplatePass) {
tView.expandoStartIndex++;
createTQuery(tView, new TQueryMetadata_(predicate, descend, read, isStatic), -1);
if (isStatic) {
tView.staticViewQueries = true;
}
}
const index = getCurrentQueryIndex();
const queryList: QueryList<T> =
createQueryListInLView<T>(lView, predicate, descend, read, isStatic, -1);
store(index - HEADER_OFFSET, queryList);
setCurrentQueryIndex(index + 1);
return queryList;
createLQuery<T>(lView);
}
/**
* Loads current View Query and moves the pointer/index to the next View Query in LView.
* Loads a QueryList corresponding to the current view query.
*
* @codeGenApi
*/
export function ɵɵloadViewQuery<T>(): T {
const index = getCurrentQueryIndex();
setCurrentQueryIndex(index + 1);
return loadInternal<T>(getLView(), index - HEADER_OFFSET);
export function ɵɵloadViewQuery<T>(): QueryList<T> {
return loadQueryInternal<T>(getLView(), getCurrentQueryIndex());
}
/**
@ -465,33 +486,9 @@ export function ɵɵloadViewQuery<T>(): T {
* @codeGenApi
*/
export function ɵɵcontentQuery<T>(
directiveIndex: number, predicate: Type<any>| string[], descend: boolean,
// TODO(FW-486): "read" should be an AbstractType
read: any): QueryList<T> {
const lView = getLView();
const tView = lView[TVIEW];
const tNode = getPreviousOrParentTNode();
return contentQueryInternal(
lView, tView, directiveIndex, predicate, descend, read, false, tNode.index);
}
function contentQueryInternal<T>(
lView: LView, tView: TView, directiveIndex: number, predicate: Type<any>| string[],
descend: boolean,
// TODO(FW-486): "read" should be an AbstractType
read: any, isStatic: boolean, nodeIndex: number): QueryList<T> {
const contentQuery: QueryList<T> =
createQueryListInLView<T>(lView, predicate, descend, read, isStatic, nodeIndex);
(lView[CONTENT_QUERIES] || (lView[CONTENT_QUERIES] = [])).push(contentQuery);
if (tView.firstTemplatePass) {
const tViewContentQueries = tView.contentQueries || (tView.contentQueries = []);
const lastSavedDirectiveIndex =
tView.contentQueries.length ? tView.contentQueries[tView.contentQueries.length - 1] : -1;
if (directiveIndex !== lastSavedDirectiveIndex) {
tViewContentQueries.push(directiveIndex);
}
}
return contentQuery;
directiveIndex: number, predicate: Type<any>| string[], descend: boolean, read: any): void {
contentQueryInternal(
getLView(), predicate, descend, read, false, getPreviousOrParentTNode(), directiveIndex);
}
/**
@ -507,29 +504,65 @@ function contentQueryInternal<T>(
* @codeGenApi
*/
export function ɵɵstaticContentQuery<T>(
directiveIndex: number, predicate: Type<any>| string[], descend: boolean,
// TODO(FW-486): "read" should be an AbstractType
read: any): void {
const lView = getLView();
directiveIndex: number, predicate: Type<any>| string[], descend: boolean, read: any): void {
contentQueryInternal(
getLView(), predicate, descend, read, true, getPreviousOrParentTNode(), directiveIndex);
}
function contentQueryInternal<T>(
lView: LView, predicate: Type<any>| string[], descend: boolean, read: any, isStatic: boolean,
tNode: TNode, directiveIndex: number): void {
const tView = lView[TVIEW];
const tNode = getPreviousOrParentTNode();
contentQueryInternal(lView, tView, directiveIndex, predicate, descend, read, true, tNode.index);
tView.staticContentQueries = true;
if (tView.firstTemplatePass) {
createTQuery(tView, new TQueryMetadata_(predicate, descend, read, isStatic), tNode.index);
saveContentQueryAndDirectiveIndex(tView, directiveIndex);
if (isStatic) {
tView.staticContentQueries = true;
}
}
createLQuery<T>(lView);
}
/**
* Loads a QueryList corresponding to the current content query.
*
* @codeGenApi
*/
export function ɵɵloadContentQuery<T>(): QueryList<T> {
const lView = getLView();
ngDevMode &&
assertDefined(
lView[CONTENT_QUERIES], 'Content QueryList array should be defined if reading a query.');
const index = getCurrentQueryIndex();
ngDevMode && assertDataInRange(lView[CONTENT_QUERIES] !, index);
setCurrentQueryIndex(index + 1);
return lView[CONTENT_QUERIES] ![index];
return loadQueryInternal<T>(getLView(), getCurrentQueryIndex());
}
function loadQueryInternal<T>(lView: LView, queryIndex: number): QueryList<T> {
ngDevMode &&
assertDefined(lView[QUERIES], 'LQueries should be defined when trying to load a query');
ngDevMode && assertDataInRange(lView[QUERIES] !.queries, queryIndex);
return lView[QUERIES] !.queries[queryIndex].queryList;
}
function createLQuery<T>(lView: LView) {
const queryList = new QueryList<T>();
storeCleanupWithContext(lView, queryList, queryList.destroy);
if (lView[QUERIES] === null) lView[QUERIES] = new LQueries_();
lView[QUERIES] !.queries.push(new LQuery_(queryList));
}
function createTQuery(tView: TView, metadata: TQueryMetadata, nodeIndex: number): void {
if (tView.queries === null) tView.queries = new TQueries_();
tView.queries.track(new TQuery_(metadata, nodeIndex));
}
function saveContentQueryAndDirectiveIndex(tView: TView, directiveIndex: number) {
const tViewContentQueries = tView.contentQueries || (tView.contentQueries = []);
const lastSavedDirectiveIndex =
tView.contentQueries.length ? tViewContentQueries[tViewContentQueries.length - 1] : -1;
if (directiveIndex !== lastSavedDirectiveIndex) {
tViewContentQueries.push(tView.queries !.length - 1, directiveIndex);
}
}
function getTQuery(tView: TView, index: number): TQuery {
ngDevMode && assertDefined(tView.queries, 'TQueries must be defined to retrieve a TQuery');
return tView.queries !.getByIndex(index);
}

View File

@ -17,13 +17,16 @@ import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, ViewRef as viewEngine_Vie
import {Renderer2} from '../render/api';
import {assertDefined, assertGreaterThan, assertLessThan} from '../util/assert';
import {assertLContainer} from './assert';
import {NodeInjector, getParentInjectorLocation} from './di';
import {addToViewTree, createEmbeddedViewAndNode, createLContainer, renderEmbeddedTemplate} from './instructions/shared';
import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer, VIEW_REFS} from './interfaces/container';
import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeType, TViewNode} from './interfaces/node';
import {RComment, RElement, isProceduralRenderer} from './interfaces/renderer';
import {isComponent, isLContainer, isLView, isRootView} from './interfaces/type_checks';
import {CONTEXT, LView, QUERIES, RENDERER, TView, T_HOST} from './interfaces/view';
import {CONTEXT, DECLARATION_LCONTAINER, LView, QUERIES, RENDERER, TView, T_HOST} from './interfaces/view';
import {assertNodeOfPossibleTypes} from './node_assert';
import {addRemoveViewFromContainer, appendChild, detachView, getBeforeNodeForView, insertView, nativeInsertBefore, nativeNextSibling, nativeParentNode, removeView} from './node_manipulation';
import {getParentInjectorTNode} from './node_util';
@ -66,9 +69,8 @@ export function createElementRef(
}
let R3TemplateRef: {
new (
_declarationParentView: LView, elementRef: ViewEngine_ElementRef, _tView: TView,
_hostLContainer: LContainer, _injectorIndex: number): ViewEngine_TemplateRef<any>
new (_declarationParentView: LView, hostTNode: TContainerNode, elementRef: ViewEngine_ElementRef):
ViewEngine_TemplateRef<any>
};
/**
@ -88,9 +90,9 @@ export function injectTemplateRef<T>(
*
* @param TemplateRefToken The TemplateRef type
* @param ElementRefToken The ElementRef type
* @param hostTNode The node that is requesting a TemplateRef
* @param hostTNode The node on which a TemplateRef is requested
* @param hostView The view to which the node belongs
* @returns The TemplateRef instance to use
* @returns The TemplateRef instance or null if we can't create a TemplateRef on a given node type
*/
export function createTemplateRef<T>(
TemplateRefToken: typeof ViewEngine_TemplateRef, ElementRefToken: typeof ViewEngine_ElementRef,
@ -99,27 +101,27 @@ export function createTemplateRef<T>(
// TODO: Fix class name, should be TemplateRef, but there appears to be a rollup bug
R3TemplateRef = class TemplateRef_<T> extends TemplateRefToken<T> {
constructor(
private _declarationParentView: LView, readonly elementRef: ViewEngine_ElementRef,
private _tView: TView, private _hostLContainer: LContainer,
private _injectorIndex: number) {
private _declarationView: LView, private _declarationTContainer: TContainerNode,
readonly elementRef: ViewEngine_ElementRef) {
super();
}
createEmbeddedView(context: T, container?: LContainer, index?: number):
viewEngine_EmbeddedViewRef<T> {
const currentQueries = this._declarationParentView[QUERIES];
// Query container may be missing if this view was created in a directive
// constructor. Create it now to avoid losing results in embedded views.
if (currentQueries && this._hostLContainer[QUERIES] == null) {
this._hostLContainer[QUERIES] = currentQueries !.container();
}
createEmbeddedView(context: T): viewEngine_EmbeddedViewRef<T> {
const embeddedTView = this._declarationTContainer.tViews as TView;
const lView = createEmbeddedViewAndNode(
this._tView, context, this._declarationParentView, this._hostLContainer[QUERIES],
this._injectorIndex);
if (container) {
insertView(lView, container, index !);
embeddedTView, context, this._declarationView,
this._declarationTContainer.injectorIndex);
const declarationLContainer = this._declarationView[this._declarationTContainer.index];
ngDevMode && assertLContainer(declarationLContainer);
lView[DECLARATION_LCONTAINER] = declarationLContainer;
const declarationViewLQueries = this._declarationView[QUERIES];
if (declarationViewLQueries !== null) {
lView[QUERIES] = declarationViewLQueries.createEmbeddedView(embeddedTView);
}
renderEmbeddedTemplate(lView, this._tView, context);
renderEmbeddedTemplate(lView, embeddedTView, context);
const viewRef = new ViewRef(lView, context, -1);
viewRef._tViewNode = lView[T_HOST] as TViewNode;
return viewRef;
@ -128,11 +130,10 @@ export function createTemplateRef<T>(
}
if (hostTNode.type === TNodeType.Container) {
const hostContainer: LContainer = hostView[hostTNode.index];
ngDevMode && assertDefined(hostTNode.tViews, 'TView must be allocated');
return new R3TemplateRef(
hostView, createElementRef(ElementRefToken, hostTNode, hostView), hostTNode.tViews as TView,
hostContainer, hostTNode.injectorIndex);
hostView, hostTNode as TContainerNode,
createElementRef(ElementRefToken, hostTNode, hostView));
} else {
return null;
}
@ -218,12 +219,8 @@ export function createContainerRef(
createEmbeddedView<C>(templateRef: ViewEngine_TemplateRef<C>, context?: C, index?: number):
viewEngine_EmbeddedViewRef<C> {
this.allocateContainerIfNeeded();
const adjustedIdx = this._adjustIndex(index);
const viewRef = (templateRef as any)
.createEmbeddedView(context || <any>{}, this._lContainer, adjustedIdx);
(viewRef as ViewRef<any>).attachToViewContainerRef(this);
this._lContainer[VIEW_REFS] !.splice(adjustedIdx, 0, viewRef);
const viewRef = templateRef.createEmbeddedView(context || <any>{});
this.insert(viewRef, index);
return viewRef;
}

View File

@ -7,7 +7,7 @@
*/
import {CommonModule} from '@angular/common';
import {Component, ContentChild, ContentChildren, Directive, ElementRef, Input, QueryList, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef} from '@angular/core';
import {Component, ContentChild, ContentChildren, Directive, ElementRef, Input, QueryList, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef, forwardRef} from '@angular/core';
import {TestBed} from '@angular/core/testing';
import {By} from '@angular/platform-browser';
import {expect} from '@angular/platform-browser/testing/src/matchers';
@ -786,6 +786,56 @@ describe('query logic', () => {
expect(queryList.last.nativeElement.id).toBe('c');
});
it('should support a mix of content queries from the declaration and embedded view', () => {
@Directive({selector: '[query-for-lots-of-content]'})
class QueryForLotsOfContent {
@ContentChildren('foo', {descendants: true}) foos1 !: QueryList<ElementRef>;
@ContentChildren('foo', {descendants: true}) foos2 !: QueryList<ElementRef>;
}
@Directive({selector: '[query-for-content]'})
class QueryForContent {
@ContentChildren('foo') foos !: QueryList<ElementRef>;
}
@Component({
selector: 'test-comp',
template: `
<div query-for-lots-of-content>
<ng-template ngFor let-item [ngForOf]="items">
<div query-for-content>
<span #foo></span>
</div>
</ng-template>
</div>
`
})
class TestComponent {
items = [1, 2];
}
TestBed.configureTestingModule(
{declarations: [TestComponent, QueryForContent, QueryForLotsOfContent]});
const fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
const lotsOfContentEl = fixture.debugElement.query(By.directive(QueryForLotsOfContent));
const lotsOfContentInstance = lotsOfContentEl.injector.get(QueryForLotsOfContent);
const contentEl = fixture.debugElement.query(By.directive(QueryForContent));
const contentInstance = contentEl.injector.get(QueryForContent);
expect(lotsOfContentInstance.foos1.length).toBe(2);
expect(lotsOfContentInstance.foos2.length).toBe(2);
expect(contentInstance.foos.length).toBe(1);
fixture.componentInstance.items = [];
fixture.detectChanges();
expect(lotsOfContentInstance.foos1.length).toBe(0);
expect(lotsOfContentInstance.foos2.length).toBe(0);
});
// https://stackblitz.com/edit/angular-rrmmuf?file=src/app/app.component.ts
it('should report results when different instances of TemplateRef are inserted into one ViewContainerRefs',
() => {
@ -864,12 +914,11 @@ describe('query logic', () => {
// https://stackblitz.com/edit/angular-7vvo9j?file=src%2Fapp%2Fapp.component.ts
// https://stackblitz.com/edit/angular-xzwp6n
onlyInIvy('FW-1318: QueryList entries are ordered differently in Ivy.')
.it('should report results when the same TemplateRef is inserted into different ViewContainerRefs',
() => {
@Component({
selector: 'test-comp',
template: `
it('should report results when the same TemplateRef is inserted into different ViewContainerRefs',
() => {
@Component({
selector: 'test-comp',
template: `
<ng-template #tpl let-idx="idx" let-container_idx="container_idx">
<div #foo [id]="'foo_' + container_idx + '_' + idx"></div>
</ng-template>
@ -877,44 +926,44 @@ describe('query logic', () => {
<ng-template vc #vi0="vc"></ng-template>
<ng-template vc #vi1="vc"></ng-template>
`,
})
class TestComponent {
@ViewChild('tpl', {static: false}) tpl !: TemplateRef<any>;
@ViewChild('vi0', {static: false}) vi0 !: ViewContainerManipulatorDirective;
@ViewChild('vi1', {static: false}) vi1 !: ViewContainerManipulatorDirective;
@ViewChildren('foo') query !: QueryList<any>;
}
})
class TestComponent {
@ViewChild('tpl', {static: false}) tpl !: TemplateRef<any>;
@ViewChild('vi0', {static: false}) vi0 !: ViewContainerManipulatorDirective;
@ViewChild('vi1', {static: false}) vi1 !: ViewContainerManipulatorDirective;
@ViewChildren('foo') query !: QueryList<any>;
}
TestBed.configureTestingModule(
{declarations: [ViewContainerManipulatorDirective, TestComponent]});
const fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
TestBed.configureTestingModule(
{declarations: [ViewContainerManipulatorDirective, TestComponent]});
const fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges();
const queryList = fixture.componentInstance.query;
const {tpl, vi0, vi1} = fixture.componentInstance;
const queryList = fixture.componentInstance.query;
const {tpl, vi0, vi1} = fixture.componentInstance;
expect(queryList.length).toBe(0);
expect(queryList.length).toBe(0);
vi0.insertTpl(tpl !, {idx: 0, container_idx: 0}, 0);
vi1.insertTpl(tpl !, {idx: 0, container_idx: 1}, 0);
fixture.detectChanges();
vi0.insertTpl(tpl !, {idx: 0, container_idx: 0}, 0);
vi1.insertTpl(tpl !, {idx: 0, container_idx: 1}, 0);
fixture.detectChanges();
expect(queryList.length).toBe(2);
let qListArr = queryList.toArray();
expect(qListArr[0].nativeElement.getAttribute('id')).toBe('foo_1_0');
expect(qListArr[1].nativeElement.getAttribute('id')).toBe('foo_0_0');
expect(queryList.length).toBe(2);
let qListArr = queryList.toArray();
expect(qListArr[0].nativeElement.getAttribute('id')).toBe('foo_0_0');
expect(qListArr[1].nativeElement.getAttribute('id')).toBe('foo_1_0');
vi0.remove();
fixture.detectChanges();
vi0.remove();
fixture.detectChanges();
expect(queryList.length).toBe(1);
qListArr = queryList.toArray();
expect(qListArr[0].nativeElement.getAttribute('id')).toBe('foo_1_0');
expect(queryList.length).toBe(1);
qListArr = queryList.toArray();
expect(qListArr[0].nativeElement.getAttribute('id')).toBe('foo_1_0');
vi1.remove();
fixture.detectChanges();
expect(queryList.length).toBe(0);
});
vi1.remove();
fixture.detectChanges();
expect(queryList.length).toBe(0);
});
// https://stackblitz.com/edit/angular-wpd6gv?file=src%2Fapp%2Fapp.component.ts
it('should report results from views inserted in a lifecycle hook', () => {
@ -953,6 +1002,96 @@ describe('query logic', () => {
});
});
describe('non-regression', () => {
it('should query by provider super-type in an embedded view', () => {
@Directive({selector: '[child]'})
class Child {
}
@Directive({selector: '[parent]', providers: [{provide: Child, useExisting: Parent}]})
class Parent extends Child {
}
@Component({
selector: 'test-cmpt',
template:
`<ng-template [ngIf]="true"><ng-template [ngIf]="true"><div parent></div></ng-template></ng-template>`
})
class TestCmpt {
@ViewChildren(Child) instances !: QueryList<Child>;
}
TestBed.configureTestingModule({declarations: [TestCmpt, Parent, Child]});
const fixture = TestBed.createComponent(TestCmpt);
fixture.detectChanges();
expect(fixture.componentInstance.instances.length).toBe(1);
});
it('should flatten multi-provider results', () => {
class MyClass {}
@Component({
selector: 'with-multi-provider',
template: '',
providers:
[{provide: MyClass, useExisting: forwardRef(() => WithMultiProvider), multi: true}]
})
class WithMultiProvider {
}
@Component({selector: 'test-cmpt', template: `<with-multi-provider></with-multi-provider>`})
class TestCmpt {
@ViewChildren(MyClass) queryResults !: QueryList<WithMultiProvider>;
}
TestBed.configureTestingModule({declarations: [TestCmpt, WithMultiProvider]});
const fixture = TestBed.createComponent(TestCmpt);
fixture.detectChanges();
expect(fixture.componentInstance.queryResults.length).toBe(1);
expect(fixture.componentInstance.queryResults.first).toBeAnInstanceOf(WithMultiProvider);
});
it('should flatten multi-provider results when crossing ng-template', () => {
class MyClass {}
@Component({
selector: 'with-multi-provider',
template: '',
providers:
[{provide: MyClass, useExisting: forwardRef(() => WithMultiProvider), multi: true}]
})
class WithMultiProvider {
}
@Component({
selector: 'test-cmpt',
template: `
<ng-template [ngIf]="true"><with-multi-provider></with-multi-provider></ng-template>
<with-multi-provider></with-multi-provider>
`
})
class TestCmpt {
@ViewChildren(MyClass) queryResults !: QueryList<WithMultiProvider>;
}
TestBed.configureTestingModule({declarations: [TestCmpt, WithMultiProvider]});
const fixture = TestBed.createComponent(TestCmpt);
fixture.detectChanges();
expect(fixture.componentInstance.queryResults.length).toBe(2);
expect(fixture.componentInstance.queryResults.first).toBeAnInstanceOf(WithMultiProvider);
expect(fixture.componentInstance.queryResults.last).toBeAnInstanceOf(WithMultiProvider);
});
});
});
function initWithTemplate(compType: Type<any>, template: string) {

View File

@ -110,9 +110,6 @@
{
"name": "PREORDER_HOOK_FLAGS"
},
{
"name": "QUERIES"
},
{
"name": "RENDERER"
},

View File

@ -38,6 +38,9 @@
{
"name": "ChangeDetectionStrategy"
},
{
"name": "DECLARATION_LCONTAINER"
},
{
"name": "DECLARATION_VIEW"
},
@ -116,6 +119,9 @@
{
"name": "MONKEY_PATCH_KEY_NAME"
},
{
"name": "MOVED_VIEWS"
},
{
"name": "NATIVE"
},
@ -437,9 +443,6 @@
{
"name": "addRemoveViewFromContainer"
},
{
"name": "addTContainerToQueries"
},
{
"name": "addToViewTree"
},
@ -602,6 +605,9 @@
{
"name": "destroyViewTree"
},
{
"name": "detachMovedView"
},
{
"name": "detachView"
},
@ -1367,6 +1373,9 @@
{
"name": "trackByIdentity"
},
{
"name": "trackMovedView"
},
{
"name": "unwrapRNode"
},

View File

@ -1344,378 +1344,6 @@ describe('query', () => {
});
});
describe('view boundaries', () => {
describe('JS blocks', () => {
it('should report results in embedded views', () => {
let firstEl;
/**
* % if (exp) {
* <div #foo></div>
* % }
* class Cmpt {
* @ViewChildren('foo') query;
* }
*/
const Cmpt = createComponent(
'cmpt',
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵcontainer(0);
}
if (rf & RenderFlags.Update) {
ɵɵcontainerRefreshStart(0);
{
if (ctx.exp) {
let rf1 = ɵɵembeddedViewStart(1, 2, 0);
{
if (rf1 & RenderFlags.Create) {
ɵɵelement(0, 'div', null, ['foo', '']);
firstEl = getNativeByIndex(0, getLView());
}
}
ɵɵembeddedViewEnd();
}
}
ɵɵcontainerRefreshEnd();
}
},
1, 0, [], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(['foo'], true, null);
}
if (rf & RenderFlags.Update) {
let tmp: any;
ɵɵqueryRefresh(tmp = ɵɵloadViewQuery<QueryList<any>>()) &&
(ctx.query = tmp as QueryList<any>);
}
});
const cmptInstance = renderComponent(Cmpt);
const qList = (cmptInstance.query as any);
expect(qList.length).toBe(0);
cmptInstance.exp = true;
detectChanges(cmptInstance);
expect(qList.length).toBe(1);
expect(qList.first.nativeElement).toBe(firstEl);
cmptInstance.exp = false;
detectChanges(cmptInstance);
expect(qList.length).toBe(0);
});
it('should add results from embedded views in the correct order - views and elements mix',
() => {
let firstEl, lastEl, viewEl;
/**
* <span #foo></span>
* % if (exp) {
* <div #foo></div>
* % }
* <span #foo></span>
* class Cmpt {
* @ViewChildren('foo') query;
* }
*/
const Cmpt = createComponent(
'cmpt',
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵelement(0, 'span', null, ['foo', '']);
firstEl = getNativeByIndex(0, getLView());
ɵɵcontainer(2);
ɵɵelement(3, 'span', null, ['foo', '']);
lastEl = getNativeByIndex(3, getLView());
}
if (rf & RenderFlags.Update) {
ɵɵcontainerRefreshStart(2);
{
if (ctx.exp) {
let rf1 = ɵɵembeddedViewStart(1, 2, 0);
{
if (rf1 & RenderFlags.Create) {
ɵɵelement(0, 'div', null, ['foo', '']);
viewEl = getNativeByIndex(0, getLView());
}
}
ɵɵembeddedViewEnd();
}
}
ɵɵcontainerRefreshEnd();
}
},
5, 0, [], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(['foo'], true, null);
}
if (rf & RenderFlags.Update) {
let tmp: any;
ɵɵqueryRefresh(tmp = ɵɵloadViewQuery<QueryList<any>>()) &&
(ctx.query = tmp as QueryList<any>);
}
});
const cmptInstance = renderComponent(Cmpt);
const qList = (cmptInstance.query as any);
expect(qList.length).toBe(2);
expect(qList.first.nativeElement).toBe(firstEl);
expect(qList.last.nativeElement).toBe(lastEl);
cmptInstance.exp = true;
detectChanges(cmptInstance);
expect(qList.length).toBe(3);
expect(qList.toArray()[0].nativeElement).toBe(firstEl);
expect(qList.toArray()[1].nativeElement).toBe(viewEl);
expect(qList.toArray()[2].nativeElement).toBe(lastEl);
cmptInstance.exp = false;
detectChanges(cmptInstance);
expect(qList.length).toBe(2);
expect(qList.first.nativeElement).toBe(firstEl);
expect(qList.last.nativeElement).toBe(lastEl);
});
it('should add results from embedded views in the correct order - views side by side', () => {
let firstEl, lastEl;
/**
* % if (exp1) {
* <div #foo></div>
* % } if (exp2) {
* <span #foo></span>
* % }
* class Cmpt {
* @ViewChildren('foo') query;
* }
*/
const Cmpt = createComponent(
'cmpt',
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵcontainer(0);
}
if (rf & RenderFlags.Update) {
ɵɵcontainerRefreshStart(0);
{
if (ctx.exp1) {
let rf0 = ɵɵembeddedViewStart(0, 2, 0);
{
if (rf0 & RenderFlags.Create) {
ɵɵelement(0, 'div', null, ['foo', '']);
firstEl = getNativeByIndex(0, getLView());
}
}
ɵɵembeddedViewEnd();
}
if (ctx.exp2) {
let rf1 = ɵɵembeddedViewStart(1, 2, 0);
{
if (rf1 & RenderFlags.Create) {
ɵɵelement(0, 'span', null, ['foo', '']);
lastEl = getNativeByIndex(0, getLView());
}
}
ɵɵembeddedViewEnd();
}
}
ɵɵcontainerRefreshEnd();
}
},
1, 0, [], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(['foo'], true, null);
}
if (rf & RenderFlags.Update) {
let tmp: any;
ɵɵqueryRefresh(tmp = ɵɵloadViewQuery<QueryList<any>>()) &&
(ctx.query = tmp as QueryList<any>);
}
});
const cmptInstance = renderComponent(Cmpt);
const qList = (cmptInstance.query as any);
expect(qList.length).toBe(0);
cmptInstance.exp2 = true;
detectChanges(cmptInstance);
expect(qList.length).toBe(1);
expect(qList.last.nativeElement).toBe(lastEl);
cmptInstance.exp1 = true;
detectChanges(cmptInstance);
expect(qList.length).toBe(2);
expect(qList.first.nativeElement).toBe(firstEl);
expect(qList.last.nativeElement).toBe(lastEl);
});
it('should add results from embedded views in the correct order - nested views', () => {
let firstEl, lastEl;
/**
* % if (exp1) {
* <div #foo></div>
* % if (exp2) {
* <span #foo></span>
* }
* % }
* class Cmpt {
* @ViewChildren('foo') query;
* }
*/
const Cmpt = createComponent(
'cmpt',
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵcontainer(0);
}
if (rf & RenderFlags.Update) {
ɵɵcontainerRefreshStart(0);
{
if (ctx.exp1) {
let rf0 = ɵɵembeddedViewStart(0, 3, 0);
{
if (rf0 & RenderFlags.Create) {
ɵɵelement(0, 'div', null, ['foo', '']);
firstEl = getNativeByIndex(0, getLView());
ɵɵcontainer(2);
}
if (rf0 & RenderFlags.Update) {
ɵɵcontainerRefreshStart(2);
{
if (ctx.exp2) {
let rf2 = ɵɵembeddedViewStart(0, 2, 0);
{
if (rf2) {
ɵɵelement(0, 'span', null, ['foo', '']);
lastEl = getNativeByIndex(0, getLView());
}
}
ɵɵembeddedViewEnd();
}
}
ɵɵcontainerRefreshEnd();
}
}
ɵɵembeddedViewEnd();
}
}
ɵɵcontainerRefreshEnd();
}
},
1, 0, [], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(['foo'], true, null);
}
if (rf & RenderFlags.Update) {
let tmp: any;
ɵɵqueryRefresh(tmp = ɵɵloadViewQuery<QueryList<any>>()) &&
(ctx.query = tmp as QueryList<any>);
}
});
const cmptInstance = renderComponent(Cmpt);
const qList = (cmptInstance.query as any);
expect(qList.length).toBe(0);
cmptInstance.exp1 = true;
detectChanges(cmptInstance);
expect(qList.length).toBe(1);
expect(qList.first.nativeElement).toBe(firstEl);
cmptInstance.exp2 = true;
detectChanges(cmptInstance);
expect(qList.length).toBe(2);
expect(qList.first.nativeElement).toBe(firstEl);
expect(qList.last.nativeElement).toBe(lastEl);
});
/**
* What is tested here can't be achieved in the Renderer2 as all view queries are deep by
* default and can't be marked as shallow by a user.
*/
it('should support combination of deep and shallow queries', () => {
/**
* % if (exp) { ">
* <div #foo>
* <div #foo></div>
* </div>
* % }
* <span #foo></span>
* class Cmpt {
* @ViewChildren('foo') deep;
* @ViewChildren('foo') shallow;
* }
*/
const Cmpt = createComponent(
'cmpt',
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵcontainer(0);
ɵɵelement(1, 'span', null, ['foo', '']);
}
if (rf & RenderFlags.Update) {
ɵɵcontainerRefreshStart(0);
{
if (ctx.exp) {
let rf0 = ɵɵembeddedViewStart(0, 4, 0);
{
if (rf0 & RenderFlags.Create) {
ɵɵelementStart(0, 'div', null, ['foo', '']);
{ ɵɵelement(2, 'div', null, ['foo', '']); }
ɵɵelementEnd();
}
}
ɵɵembeddedViewEnd();
}
}
ɵɵcontainerRefreshEnd();
}
},
3, 0, [], [],
function(rf: RenderFlags, ctx: any) {
if (rf & RenderFlags.Create) {
ɵɵviewQuery(['foo'], true, null);
ɵɵviewQuery(['foo'], false, null);
}
if (rf & RenderFlags.Update) {
let tmp: any;
ɵɵqueryRefresh(tmp = ɵɵloadViewQuery<QueryList<any>>()) &&
(ctx.deep = tmp as QueryList<any>);
ɵɵqueryRefresh(tmp = ɵɵloadViewQuery<QueryList<any>>()) &&
(ctx.shallow = tmp as QueryList<any>);
}
});
const cmptInstance = renderComponent(Cmpt);
const deep = (cmptInstance.deep as any);
const shallow = (cmptInstance.shallow as any);
expect(deep.length).toBe(1);
expect(shallow.length).toBe(1);
cmptInstance.exp = true;
detectChanges(cmptInstance);
expect(deep.length).toBe(3);
// embedded % if blocks should behave the same way as *ngIf, namely they
// should match shallow queries on the first level of elements underneath
// the embedded view boundary.
expect(shallow.length).toBe(2);
cmptInstance.exp = false;
detectChanges(cmptInstance);
expect(deep.length).toBe(1);
expect(shallow.length).toBe(1);
});
});
});
describe('queryList', () => {
it('should be destroyed when the containing view is destroyed', () => {
let queryInstance: QueryList<any>;

View File

@ -752,7 +752,7 @@ export declare function ɵɵcontainerRefreshEnd(): void;
export declare function ɵɵcontainerRefreshStart(index: number): void;
export declare function ɵɵcontentQuery<T>(directiveIndex: number, predicate: Type<any> | string[], descend: boolean, read: any): QueryList<T>;
export declare function ɵɵcontentQuery<T>(directiveIndex: number, predicate: Type<any> | string[], descend: boolean, read: any): void;
export declare const ɵɵdefaultStyleSanitizer: StyleSanitizeFn;
@ -926,7 +926,7 @@ export declare function ɵɵload<T>(index: number): T;
export declare function ɵɵloadContentQuery<T>(): QueryList<T>;
export declare function ɵɵloadViewQuery<T>(): T;
export declare function ɵɵloadViewQuery<T>(): QueryList<T>;
export declare function ɵɵnamespaceHTML(): void;
@ -1115,7 +1115,7 @@ export declare function ɵɵtextInterpolateV(values: any[]): TsickleIssue1009;
export declare function ɵɵupdateSyntheticHostBinding<T>(propName: string, value: T | NO_CHANGE, sanitizer?: SanitizerFn | null): TsickleIssue1009;
export declare function ɵɵviewQuery<T>(predicate: Type<any> | string[], descend: boolean, read: any): QueryList<T>;
export declare function ɵɵviewQuery<T>(predicate: Type<any> | string[], descend: boolean, read: any): void;
export declare const PACKAGE_ROOT_URL: InjectionToken<string>;