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

View File

@ -69,3 +69,7 @@ export function assertLView(value: any) {
assertDefined(value, 'LView must be defined'); assertDefined(value, 'LView must be defined');
assertEqual(isLView(value), true, 'Expecting LView'); 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 {Type} from '../core';
import {Injector} from '../di/injector'; import {Injector} from '../di/injector';
import {Sanitizer} from '../sanitization/security'; import {Sanitizer} from '../sanitization/security';
import {assertDataInRange, assertEqual} from '../util/assert'; import {assertDataInRange} from '../util/assert';
import {assertComponentType} from './assert'; import {assertComponentType} from './assert';
import {getComponentDef} from './definition'; 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 {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer} from '../interfaces/container';
import {ComponentTemplate} from '../interfaces/definition'; import {ComponentTemplate} from '../interfaces/definition';
import {LocalRefExtractor, TAttributes, TContainerNode, TNode, TNodeType} from '../interfaces/node'; 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 {assertNodeType} from '../node_assert';
import {appendChild, removeView} from '../node_manipulation'; import {appendChild, removeView} from '../node_manipulation';
import {getCheckNoChangesMode, getIsParent, getLView, getPreviousOrParentTNode, setIsNotParent, setPreviousOrParentTNode} from '../state'; import {getCheckNoChangesMode, getIsParent, getLView, getPreviousOrParentTNode, setIsNotParent, setPreviousOrParentTNode} from '../state';
import {getNativeByTNode, loadInternal} from '../util/view_utils'; 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) { if (lView[TVIEW].firstTemplatePass) {
tNode.tViews = []; tNode.tViews = [];
} }
addTContainerToQueries(lView, tNode);
setIsNotParent(); setIsNotParent();
} }
@ -72,12 +72,18 @@ export function ɵɵtemplate(
// TODO: consider a separate node type for templates // TODO: consider a separate node type for templates
const tContainerNode = containerInternal(lView, index, tagName || null, attrs || null); const tContainerNode = containerInternal(lView, index, tagName || null, attrs || null);
if (tView.firstTemplatePass) { 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); -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); createDirectivesAndLocals(tView, lView, tContainerNode, localRefExtractor);
addTContainerToQueries(lView, tContainerNode);
attachPatchData(getNativeByTNode(tContainerNode, lView), lView); attachPatchData(getNativeByTNode(tContainerNode, lView), lView);
registerPostOrderHooks(tView, tContainerNode); registerPostOrderHooks(tView, tContainerNode);
setIsNotParent(); 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( function containerInternal(
lView: LView, nodeIndex: number, tagName: string | null, lView: LView, nodeIndex: number, tagName: string | null,
attrs: TAttributes | null): TContainerNode { 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 * 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 * found in the LICENSE file at https://angular.io/license
*/ */
import {assertDataInRange, assertDefined, assertEqual} from '../../util/assert'; import {assertDataInRange, assertDefined, assertEqual} from '../../util/assert';
import {assertHasParent} from '../assert'; import {assertHasParent} from '../assert';
import {attachPatchData} from '../context_discovery'; import {attachPatchData} from '../context_discovery';
import {registerPostOrderHooks} from '../hooks'; 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 {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 {assertNodeType} from '../node_assert';
import {appendChild} from '../node_manipulation'; import {appendChild} from '../node_manipulation';
import {applyOnCreateInstructions} from '../node_util'; import {applyOnCreateInstructions} from '../node_util';
@ -22,7 +24,7 @@ import {getInitialStylingValue, hasClassInput, hasStyleInput} from '../styling_n
import {setUpAttributes} from '../util/attrs_utils'; import {setUpAttributes} from '../util/attrs_utils';
import {getNativeByTNode, getTNode} from '../util/view_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); renderInitialStyling(renderer, native, tNode);
appendChild(native, tNode, lView); appendChild(native, tNode, lView);
createDirectivesAndLocals(tView, lView, tNode, localRefs);
// any immediate children of a component or template container must be pre-emptively // 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 // 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]` // static class values as well. (Note that this will be fixed once map-based `[style]`
// and `[class]` bindings work for multiple directives.) // and `[class]` bindings work for multiple directives.)
if (tView.firstTemplatePass) { if (tView.firstTemplatePass) {
ngDevMode && ngDevMode.firstTemplatePass++;
resolveDirectives(tView, lView, tNode, localRefs || null);
const inputData = initializeTNodeInputs(tNode); const inputData = initializeTNodeInputs(tNode);
if (inputData && inputData.hasOwnProperty('class')) { if (inputData && inputData.hasOwnProperty('class')) {
tNode.flags |= TNodeFlags.hasClassInput; tNode.flags |= TNodeFlags.hasClassInput;
@ -89,13 +93,13 @@ export function ɵɵelementStart(
if (inputData && inputData.hasOwnProperty('style')) { if (inputData && inputData.hasOwnProperty('style')) {
tNode.flags |= TNodeFlags.hasStyleInput; tNode.flags |= TNodeFlags.hasStyleInput;
} }
if (tView.queries !== null) {
tView.queries.elementStart(tView, tNode);
}
} }
const currentQueries = lView[QUERIES]; createDirectivesAndLocals(tView, lView, tNode);
if (currentQueries) {
currentQueries.addNode(tNode);
lView[QUERIES] = currentQueries.clone(tNode);
}
executeContentQueries(tView, tNode, lView); executeContentQueries(tView, tNode, lView);
} }
@ -123,15 +127,16 @@ export function ɵɵelementEnd(): void {
ngDevMode && assertNodeType(tNode, TNodeType.Element); ngDevMode && assertNodeType(tNode, TNodeType.Element);
const lView = getLView(); const lView = getLView();
const currentQueries = lView[QUERIES]; const tView = lView[TVIEW];
// 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;
}
registerPostOrderHooks(lView[TVIEW], tNode); registerPostOrderHooks(tView, previousOrParentTNode);
decreaseElementDepthCount(); decreaseElementDepthCount();
if (tView.firstTemplatePass && tView.queries !== null &&
isContentQueryHost(previousOrParentTNode)) {
tView.queries !.elementEnd(previousOrParentTNode);
}
if (hasClassInput(tNode) && tNode.classes) { if (hasClassInput(tNode) && tNode.classes) {
setDirectiveStylingInput(tNode.classes, lView, tNode.inputs !['class']); setDirectiveStylingInput(tNode.classes, lView, tNode.inputs !['class']);
} }

View File

@ -10,14 +10,15 @@ import {assertHasParent} from '../assert';
import {attachPatchData} from '../context_discovery'; import {attachPatchData} from '../context_discovery';
import {registerPostOrderHooks} from '../hooks'; import {registerPostOrderHooks} from '../hooks';
import {TAttributes, TNodeType} from '../interfaces/node'; 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 {assertNodeType} from '../node_assert';
import {appendChild} from '../node_manipulation'; import {appendChild} from '../node_manipulation';
import {applyOnCreateInstructions} from '../node_util'; import {applyOnCreateInstructions} from '../node_util';
import {getIsParent, getLView, getPreviousOrParentTNode, setIsNotParent, setPreviousOrParentTNode} from '../state'; import {getIsParent, getLView, getPreviousOrParentTNode, setIsNotParent, setPreviousOrParentTNode} from '../state';
import {registerInitialStylingOnTNode} from '../styling_next/instructions'; 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); appendChild(native, tNode, lView);
createDirectivesAndLocals(tView, lView, tNode, localRefs);
attachPatchData(native, lView);
const currentQueries = lView[QUERIES]; if (tView.firstTemplatePass) {
if (currentQueries) { ngDevMode && ngDevMode.firstTemplatePass++;
currentQueries.addNode(tNode); resolveDirectives(tView, lView, tNode, localRefs || null);
lView[QUERIES] = currentQueries.clone(tNode); if (tView.queries) {
tView.queries.elementStart(tView, tNode);
}
} }
createDirectivesAndLocals(tView, lView, tNode);
attachPatchData(native, lView);
executeContentQueries(tView, tNode, lView); executeContentQueries(tView, tNode, lView);
} }
@ -90,17 +94,17 @@ export function ɵɵelementContainerEnd(): void {
} }
ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.ElementContainer); 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 // this is required for all host-level styling-related instructions to run
// in the correct order // in the correct order
previousOrParentTNode.onElementCreationFns && applyOnCreateInstructions(previousOrParentTNode); previousOrParentTNode.onElementCreationFns && applyOnCreateInstructions(previousOrParentTNode);
registerPostOrderHooks(tView, 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, getOrCreateEmbeddedTView(viewBlockId, consts, vars, containerTNode as TContainerNode), null,
LViewFlags.CheckAlways, null, null); LViewFlags.CheckAlways, null, null);
if (lContainer[QUERIES]) {
viewToRender[QUERIES] = lContainer[QUERIES] !.createView();
}
const tParentNode = getIsParent() ? previousOrParentTNode : const tParentNode = getIsParent() ? previousOrParentTNode :
previousOrParentTNode && previousOrParentTNode.parent; previousOrParentTNode && previousOrParentTNode.parent;
assignTViewNodeToLView(viewToRender[TVIEW], tParentNode, viewBlockId, viewToRender); assignTViewNodeToLView(viewToRender[TVIEW], tParentNode, viewBlockId, viewToRender);

View File

@ -10,15 +10,16 @@ import {AttributeMarker, ComponentTemplate} from '..';
import {SchemaMetadata} from '../../core'; import {SchemaMetadata} from '../../core';
import {assertDefined} from '../../util/assert'; import {assertDefined} from '../../util/assert';
import {createNamedArrayType} from '../../util/named_array_type'; 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 {DirectiveDefList, PipeDefList, ViewQueriesFunction} from '../interfaces/definition';
import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, TIcu} from '../interfaces/i18n'; 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 {PropertyAliases, TContainerNode, TElementNode, TNode as ITNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TViewNode} from '../interfaces/node';
import {SelectorFlags} from '../interfaces/projection'; import {SelectorFlags} from '../interfaces/projection';
import {LQueries} from '../interfaces/query'; import {TQueries} from '../interfaces/query';
import {RComment, RElement, RNode} from '../interfaces/renderer'; import {RComment, RElement, RNode} from '../interfaces/renderer';
import {StylingContext} from '../interfaces/styling'; 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 {TStylingContext} from '../styling_next/interfaces';
import {DebugStyling as DebugNewStyling, NodeStylingDebug} from '../styling_next/styling_debug'; import {DebugStyling as DebugNewStyling, NodeStylingDebug} from '../styling_next/styling_debug';
import {isStylingContext} from '../styling_next/util'; import {isStylingContext} from '../styling_next/util';
@ -78,11 +79,11 @@ export const TViewConstructor = class TView implements ITView {
public id: number, // public id: number, //
public blueprint: LView, // public blueprint: LView, //
public template: ComponentTemplate<{}>|null, // public template: ComponentTemplate<{}>|null, //
public queries: TQueries|null, //
public viewQuery: ViewQueriesFunction<{}>|null, // public viewQuery: ViewQueriesFunction<{}>|null, //
public node: TViewNode|TElementNode|null, // public node: TViewNode|TElementNode|null, //
public data: TData, // public data: TData, //
public bindingStartIndex: number, // public bindingStartIndex: number, //
public viewQueryStartIndex: number, //
public expandoStartIndex: number, // public expandoStartIndex: number, //
public expandoInstructions: ExpandoInstructions|null, // public expandoInstructions: ExpandoInstructions|null, //
public firstTemplatePass: boolean, // public firstTemplatePass: boolean, //
@ -287,8 +288,7 @@ export class LViewDebug {
next: toDebug(this._raw_lView[NEXT]), next: toDebug(this._raw_lView[NEXT]),
childTail: toDebug(this._raw_lView[CHILD_TAIL]), childTail: toDebug(this._raw_lView[CHILD_TAIL]),
declarationView: toDebug(this._raw_lView[DECLARATION_VIEW]), declarationView: toDebug(this._raw_lView[DECLARATION_VIEW]),
contentQueries: this._raw_lView[CONTENT_QUERIES], queries: null,
queries: this._raw_lView[QUERIES],
tHost: this._raw_lView[T_HOST], tHost: this._raw_lView[T_HOST],
bindingIndex: this._raw_lView[BINDING_INDEX], bindingIndex: this._raw_lView[BINDING_INDEX],
}; };
@ -361,7 +361,7 @@ export class LContainerDebug {
.map(toDebug as(l: LView) => LViewDebug); .map(toDebug as(l: LView) => LViewDebug);
} }
get parent(): LViewDebug|LContainerDebug|null { return toDebug(this._raw_lContainer[PARENT]); } 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 host(): RElement|RComment|StylingContext|LView { return this._raw_lContainer[HOST]; }
get native(): RComment { return this._raw_lContainer[NATIVE]; } get native(): RComment { return this._raw_lContainer[NATIVE]; }
get __other__() { 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 {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, FactoryFn, PipeDefListOrFactory, RenderFlags, ViewQueriesFunction} from '../interfaces/definition';
import {INJECTOR_BLOOM_PARENT_SIZE, NodeInjectorFactory} from '../interfaces/injector'; 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 {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 {RComment, RElement, RText, Renderer3, RendererFactory3, isProceduralRenderer} from '../interfaces/renderer';
import {SanitizerFn} from '../interfaces/sanitization'; import {SanitizerFn} from '../interfaces/sanitization';
import {StylingContext} from '../interfaces/styling'; 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 { function refreshContentQueries(tView: TView, lView: LView): void {
if (tView.contentQueries != null) { const contentQueries = tView.contentQueries;
setCurrentQueryIndex(0); if (contentQueries !== null) {
for (let i = 0; i < tView.contentQueries.length; i++) { for (let i = 0; i < contentQueries.length; i += 2) {
const directiveDefIdx = tView.contentQueries[i]; const queryStartIdx = contentQueries[i];
const directiveDef = tView.data[directiveDefIdx] as DirectiveDef<any>; const directiveDefIdx = contentQueries[i + 1];
ngDevMode && if (directiveDefIdx !== -1) {
assertDefined(directiveDef.contentQueries, 'contentQueries function should be defined'); const directiveDef = tView.data[directiveDefIdx] as DirectiveDef<any>;
directiveDef.contentQueries !(RenderFlags.Update, lView[directiveDefIdx], directiveDefIdx); 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). * Such lViewNode will then be renderer with renderEmbeddedTemplate() (see below).
*/ */
export function createEmbeddedViewAndNode<T>( export function createEmbeddedViewAndNode<T>(
tView: TView, context: T, declarationView: LView, queries: LQueries | null, tView: TView, context: T, declarationView: LView, injectorIndex: number): LView {
injectorIndex: number): LView {
const _isParent = getIsParent(); const _isParent = getIsParent();
const _previousOrParentTNode = getPreviousOrParentTNode(); const _previousOrParentTNode = getPreviousOrParentTNode();
setPreviousOrParentTNode(null !, true); setPreviousOrParentTNode(null !, true);
@ -370,9 +372,6 @@ export function createEmbeddedViewAndNode<T>(
const lView = createLView(declarationView, tView, context, LViewFlags.CheckAlways, null, null); const lView = createLView(declarationView, tView, context, LViewFlags.CheckAlways, null, null);
lView[DECLARATION_VIEW] = declarationView; lView[DECLARATION_VIEW] = declarationView;
if (queries) {
lView[QUERIES] = queries.createView();
}
assignTViewNodeToLView(tView, null, -1, lView); assignTViewNodeToLView(tView, null, -1, lView);
if (tView.firstTemplatePass) { if (tView.firstTemplatePass) {
@ -513,14 +512,8 @@ export function executeContentQueries(tView: TView, tNode: TNode, lView: LView)
*/ */
export function createDirectivesAndLocals( export function createDirectivesAndLocals(
tView: TView, lView: LView, tNode: TElementNode | TContainerNode | TElementContainerNode, tView: TView, lView: LView, tNode: TElementNode | TContainerNode | TElementContainerNode,
localRefs: string[] | null | undefined,
localRefExtractor: LocalRefExtractor = getNativeByTNode) { localRefExtractor: LocalRefExtractor = getNativeByTNode) {
if (!getBindingsEnabled()) return; if (!getBindingsEnabled()) return;
if (tView.firstTemplatePass) {
ngDevMode && ngDevMode.firstTemplatePass++;
resolveDirectives(
tView, lView, findDirectiveMatches(tView, lView, tNode), tNode, localRefs || null);
}
instantiateAllDirectives(tView, lView, tNode); instantiateAllDirectives(tView, lView, tNode);
invokeDirectivesHostBindings(tView, lView, tNode); invokeDirectivesHostBindings(tView, lView, tNode);
saveResolvedLocalsInData(lView, tNode, localRefExtractor); saveResolvedLocalsInData(lView, tNode, localRefExtractor);
@ -588,11 +581,11 @@ export function createTView(
viewIndex, // id: number, viewIndex, // id: number,
blueprint, // blueprint: LView, blueprint, // blueprint: LView,
templateFn, // template: ComponentTemplate<{}>|null, templateFn, // template: ComponentTemplate<{}>|null,
null, // queries: TQueries|null
viewQuery, // viewQuery: ViewQueriesFunction<{}>|null, viewQuery, // viewQuery: ViewQueriesFunction<{}>|null,
null !, // node: TViewNode|TElementNode|null, null !, // node: TViewNode|TElementNode|null,
cloneToTViewData(blueprint).fill(null, bindingStartIndex), // data: TData, cloneToTViewData(blueprint).fill(null, bindingStartIndex), // data: TData,
bindingStartIndex, // bindingStartIndex: number, bindingStartIndex, // bindingStartIndex: number,
initialViewLength, // viewQueryStartIndex: number,
initialViewLength, // expandoStartIndex: number, initialViewLength, // expandoStartIndex: number,
null, // expandoInstructions: ExpandoInstructions|null, null, // expandoInstructions: ExpandoInstructions|null,
true, // firstTemplatePass: boolean, true, // firstTemplatePass: boolean,
@ -619,11 +612,11 @@ export function createTView(
id: viewIndex, id: viewIndex,
blueprint: blueprint, blueprint: blueprint,
template: templateFn, template: templateFn,
queries: null,
viewQuery: viewQuery, viewQuery: viewQuery,
node: null !, node: null !,
data: blueprint.slice().fill(null, bindingStartIndex), data: blueprint.slice().fill(null, bindingStartIndex),
bindingStartIndex: bindingStartIndex, bindingStartIndex: bindingStartIndex,
viewQueryStartIndex: initialViewLength,
expandoStartIndex: initialViewLength, expandoStartIndex: initialViewLength,
expandoInstructions: null, expandoInstructions: null,
firstTemplatePass: true, firstTemplatePass: true,
@ -1031,13 +1024,18 @@ export function instantiateRootComponent<T>(
/** /**
* Resolve the matched directives on a node. * Resolve the matched directives on a node.
*/ */
function resolveDirectives( export function resolveDirectives(
tView: TView, viewData: LView, directives: DirectiveDef<any>[] | null, tNode: TNode, tView: TView, lView: LView, tNode: TElementNode | TContainerNode | TElementContainerNode,
localRefs: string[] | null): void { localRefs: string[] | null): void {
// Please make sure to have explicit type for `exportsMap`. Inferred type triggers bug in // Please make sure to have explicit type for `exportsMap`. Inferred type triggers bug in
// tsickle. // tsickle.
ngDevMode && assertEqual(tView.firstTemplatePass, true, 'should run on first template pass only'); 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; const exportsMap: ({[key: string]: number} | null) = localRefs ? {'': -1} : null;
if (directives) { if (directives) {
initNodeFlags(tNode, tView.data.length, directives.length); initNodeFlags(tNode, tView.data.length, directives.length);
// When the same token is provided by several directives on the same node, some rules apply in // 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 def = directives[i] as DirectiveDef<any>;
const directiveDefIdx = tView.data.length; const directiveDefIdx = tView.data.length;
baseResolveDirective(tView, viewData, def, def.factory); baseResolveDirective(tView, lView, def, def.factory);
saveNameToExportMap(tView.data !.length - 1, def, exportsMap); 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 { function executeViewQueryFn<T>(flags: RenderFlags, tView: TView, component: T): void {
const viewQuery = tView.viewQuery; const viewQuery = tView.viewQuery;
if (viewQuery) { if (viewQuery !== null) {
setCurrentQueryIndex(tView.viewQueryStartIndex); setCurrentQueryIndex(0);
viewQuery(flags, component); viewQuery(flags, component);
} }
} }

View File

@ -9,9 +9,9 @@
import {ViewRef} from '../../linker/view_ref'; import {ViewRef} from '../../linker/view_ref';
import {TNode} from './node'; import {TNode} from './node';
import {LQueries} from './query';
import {RComment, RElement} from './renderer'; 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. * Uglify will inline these when minifying so there shouldn't be a cost.
*/ */
export const ACTIVE_INDEX = 2; 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. // 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 NATIVE = 7;
export const VIEW_REFS = 8; export const VIEW_REFS = 8;
@ -84,11 +91,11 @@ export interface LContainer extends Array<any> {
[NEXT]: LView|LContainer|null; [NEXT]: LView|LContainer|null;
/** /**
* Queries active for this container - all the views inserted to / removed from * A collection of views created based on the underlying `<ng-template>` element but inserted into
* this container are reported to queries referenced here. * 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 [MOVED_VIEWS]: LView[]|null;
// `[QUERIES]` in it which are not needed for `LContainer` (only needed for Template)
/** /**
* Pointer to the `TNode` which represents the host of the container. * Pointer to the `TNode` which represents the host of the container.

View File

@ -9,99 +9,211 @@
import {Type} from '../../interface/type'; import {Type} from '../../interface/type';
import {QueryList} from '../../linker'; 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 { export interface LQueries {
/** /**
* The parent LQueries instance. * A collection of queries tracked in a given view.
*
* 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.
*/ */
parent: LQueries|null; queries: LQuery<any>[];
/** /**
* The index of the node on which this LQueries instance was created / cloned in a given LView. * 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.
* This index is stored to minimize LQueries cloning: we can observe that LQueries can be mutated * @param tView
* 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.
*/ */
nodeIndex: number; createEmbeddedView(tView: TView): LQueries|null;
/** /**
* Ask queries to prepare a copy of itself. This ensures that: * A method called when an embedded view is inserted into a container. As a result all impacted
* - tracking new queries on content nodes doesn't mutate list of queries tracked on a parent * `LQuery` objects (and associated `QueryList`) are marked as dirty.
* node; * @param tView
* - we don't track shallow queries when descending into elements hierarchy.
*
* We will clone LQueries before constructing content queries
*/ */
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 * A method called when an embedded view is detached from a container. As a result all impacted
* if matching query predicate. * `LQuery` objects (and associated `QueryList`) are marked as dirty.
* @param tView
*/ */
addNode(tNode: TElementNode|TContainerNode|TElementContainerNode): void; detachView(tView: TView): 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;
} }
// Note: This hack is necessary so we don't erroneously get a circular dependency // Note: This hack is necessary so we don't erroneously get a circular dependency
// failure based on types. // failure based on types.
export const unusedValueExportToPlacateAjd = 1; export const unusedValueExportToPlacateAjd = 1;

View File

@ -9,7 +9,6 @@
import {InjectionToken} from '../../di/injection_token'; import {InjectionToken} from '../../di/injection_token';
import {Injector} from '../../di/injector'; import {Injector} from '../../di/injector';
import {Type} from '../../interface/type'; import {Type} from '../../interface/type';
import {QueryList} from '../../linker';
import {SchemaMetadata} from '../../metadata'; import {SchemaMetadata} from '../../metadata';
import {Sanitizer} from '../../sanitization/security'; import {Sanitizer} from '../../sanitization/security';
@ -18,7 +17,7 @@ import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefList, HostBin
import {I18nUpdateOpCodes, TI18n} from './i18n'; import {I18nUpdateOpCodes, TI18n} from './i18n';
import {TElementNode, TNode, TViewNode} from './node'; import {TElementNode, TNode, TViewNode} from './node';
import {PlayerHandler} from './player'; import {PlayerHandler} from './player';
import {LQueries} from './query'; import {LQueries, TQueries} from './query';
import {RElement, Renderer3, RendererFactory3} from './renderer'; import {RElement, Renderer3, RendererFactory3} from './renderer';
@ -42,11 +41,11 @@ export const RENDERER = 12;
export const SANITIZER = 13; export const SANITIZER = 13;
export const CHILD_HEAD = 14; export const CHILD_HEAD = 14;
export const CHILD_TAIL = 15; export const CHILD_TAIL = 15;
export const CONTENT_QUERIES = 16; export const DECLARATION_VIEW = 16;
export const DECLARATION_VIEW = 17; export const DECLARATION_LCONTAINER = 17;
export const PREORDER_HOOK_FLAGS = 18; export const PREORDER_HOOK_FLAGS = 18;
/** Size of LView's header. Necessary to adjust for it when setting slots. */ /** 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 // 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; [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. * View where this view's template was declared.
* *
@ -212,6 +204,16 @@ export interface LView extends Array<any> {
*/ */
[DECLARATION_VIEW]: LView|null; [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. * More flags for this view. See PreOrderHookFlags for more info.
*/ */
@ -410,17 +412,6 @@ export interface TView {
*/ */
staticContentQueries: boolean; 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. * A reference to the first child node located in the view.
*/ */
@ -550,7 +541,19 @@ export interface TView {
components: number[]|null; 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; contentQueries: number[]|null;

View File

@ -11,18 +11,18 @@ import {assertDefined, assertDomNode} from '../util/assert';
import {assertLContainer, assertLView} from './assert'; import {assertLContainer, assertLView} from './assert';
import {attachPatchData} from './context_discovery'; 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 {ComponentDef} from './interfaces/definition';
import {NodeInjectorFactory} from './interfaces/injector'; import {NodeInjectorFactory} from './interfaces/injector';
import {TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeType, TProjectionNode, TViewNode, unusedValueExportToPlacateAjd as unused2} from './interfaces/node'; import {TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeType, TProjectionNode, TViewNode, unusedValueExportToPlacateAjd as unused2} from './interfaces/node';
import {unusedValueExportToPlacateAjd as unused3} from './interfaces/projection'; import {unusedValueExportToPlacateAjd as unused3} from './interfaces/projection';
import {ProceduralRenderer3, RElement, RNode, RText, Renderer3, isProceduralRenderer, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer'; import {ProceduralRenderer3, RElement, RNode, RText, Renderer3, isProceduralRenderer, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer';
import {isLContainer, isLView, isRootView} from './interfaces/type_checks'; 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 {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
import {renderStringify} from './util/misc_utils'; import {renderStringify} from './util/misc_utils';
import {findComponentView, getLViewParent} from './util/view_traversal_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; const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4 + unused5;
@ -217,15 +217,46 @@ export function insertView(lView: LView, lContainer: LContainer, index: number)
lView[PARENT] = lContainer; lView[PARENT] = lContainer;
// Notify query that a new view has been added // track views where declaration and insertion points are different
if (lView[QUERIES]) { const declarationLContainer = lView[DECLARATION_LCONTAINER];
lView[QUERIES] !.insertView(index); 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 // Sets the attached flag
lView[FLAGS] |= LViewFlags.Attached; 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. * 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 indexInContainer = CONTAINER_HEADER_OFFSET + removeIndex;
const viewToDetach = lContainer[indexInContainer]; const viewToDetach = lContainer[indexInContainer];
if (viewToDetach) { if (viewToDetach) {
const declarationLContainer = viewToDetach[DECLARATION_LCONTAINER];
if (declarationLContainer !== null && declarationLContainer !== lContainer) {
detachMovedView(declarationLContainer, viewToDetach);
}
if (removeIndex > 0) { if (removeIndex > 0) {
lContainer[indexInContainer - 1][NEXT] = viewToDetach[NEXT] as LView; 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); addRemoveViewFromContainer(viewToDetach, false);
if ((viewToDetach[FLAGS] & LViewFlags.Attached) && // notify query that a view has been removed
!(viewToDetach[FLAGS] & LViewFlags.Destroyed) && viewToDetach[QUERIES]) { const lQueries = removedLView[QUERIES];
viewToDetach[QUERIES] !.removeView(); if (lQueries !== null) {
lQueries.detachView(removedLView[TVIEW]);
} }
viewToDetach[PARENT] = null; viewToDetach[PARENT] = null;
viewToDetach[NEXT] = null; viewToDetach[NEXT] = null;
// Unsets the attached flag // Unsets the attached flag
@ -342,9 +382,20 @@ function cleanUpView(view: LView | LContainer): void {
ngDevMode && ngDevMode.rendererDestroy++; ngDevMode && ngDevMode.rendererDestroy++;
(view[RENDERER] as ProceduralRenderer3).destroy(); (view[RENDERER] as ProceduralRenderer3).destroy();
} }
// For embedded views still attached to a container: remove query result from this view.
if (viewAttachedToContainer(view) && view[QUERIES]) { const declarationContainer = view[DECLARATION_LCONTAINER];
view[QUERIES] !.removeView(); // 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, renderer: Renderer3, action: WalkTNodeTreeAction, lView: LView, tNode: TNode,
renderParent: RElement | null, beforeNode: RNode | null | undefined): void { renderParent: RElement | null, beforeNode: RNode | null | undefined): void {
const nodeType = tNode.type; const nodeType = tNode.type;
if (nodeType === TNodeType.ElementContainer || nodeType === TNodeType.IcuContainer) { if (!(tNode.flags & TNodeFlags.isDetached)) {
executeActionOnElementContainerOrIcuContainer( if (nodeType === TNodeType.ElementContainer || nodeType === TNodeType.IcuContainer) {
renderer, action, lView, tNode as TElementContainerNode | TIcuContainerNode, renderParent, executeActionOnElementContainerOrIcuContainer(
beforeNode); renderer, action, lView, tNode as TElementContainerNode | TIcuContainerNode, renderParent,
} else if (nodeType === TNodeType.Projection) { beforeNode);
executeActionOnProjection( } else if (nodeType === TNodeType.Projection) {
renderer, action, lView, tNode as TProjectionNode, renderParent, beforeNode); executeActionOnProjection(
} else { renderer, action, lView, tNode as TProjectionNode, renderParent, beforeNode);
ngDevMode && assertNodeOfPossibleTypes(tNode, TNodeType.Element, TNodeType.Container); } else {
executeActionOnElementOrContainer( ngDevMode && assertNodeOfPossibleTypes(tNode, TNodeType.Element, TNodeType.Container);
action, renderer, renderParent, lView[tNode.index], beforeNode); 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 {ElementRef as ViewEngine_ElementRef} from '../linker/element_ref';
import {QueryList} from '../linker/query_list'; import {QueryList} from '../linker/query_list';
import {TemplateRef as ViewEngine_TemplateRef} from '../linker/template_ref'; 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 {getNodeInjectable, locateDirectiveOrProvider} from './di';
import {NG_ELEMENT_ID} from './fields';
import {store} from './instructions/all';
import {storeCleanupWithContext} from './instructions/shared'; 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 unused1} from './interfaces/definition';
import {unusedValueExportToPlacateAjd as unused2} from './interfaces/injector'; import {unusedValueExportToPlacateAjd as unused2} from './interfaces/injector';
import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeType, unusedValueExportToPlacateAjd as unused3} from './interfaces/node'; import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeType, unusedValueExportToPlacateAjd as unused3} from './interfaces/node';
import {LQueries, unusedValueExportToPlacateAjd as unused4} from './interfaces/query'; import {LQueries, LQuery, TQueries, TQuery, TQueryMetadata, unusedValueExportToPlacateAjd as unused4} from './interfaces/query';
import {isContentQueryHost} from './interfaces/type_checks'; import {DECLARATION_LCONTAINER, LView, PARENT, QUERIES, TVIEW, TView} from './interfaces/view';
import {CONTENT_QUERIES, HEADER_OFFSET, LView, QUERIES, TVIEW, TView} from './interfaces/view'; import {assertNodeOfPossibleTypes} from './node_assert';
import {getCurrentQueryIndex, getIsParent, getLView, getPreviousOrParentTNode, isCreationMode, setCurrentQueryIndex} from './state'; import {getCurrentQueryIndex, getLView, getPreviousOrParentTNode, isCreationMode, setCurrentQueryIndex} from './state';
import {loadInternal} from './util/view_utils'; import {createContainerRef, createElementRef, createTemplateRef} from './view_engine_compatibility';
import {createElementRef, createTemplateRef} from './view_engine_compatibility';
const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4; const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4;
/** class LQuery_<T> implements LQuery<T> {
* A predicate which determines if a given element/directive should be included in the query matches: (T|null)[]|null = null;
* results. constructor(public queryList: QueryList<T>) {}
*/ clone(): LQuery<T> { return new LQuery_(this.queryList); }
export interface QueryPredicate<T> { setDirty(): void { this.queryList.setDirty(); }
/**
* 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 LQueries_ implements LQueries {
* An object representing a query, which is a combination of: constructor(public queries: LQuery<any>[] = []) {}
* - 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,
/** createEmbeddedView(tView: TView): LQueries|null {
* Destination to which the value should be added. const tQueries = tView.queries;
*/ if (tQueries !== null) {
public list: QueryList<T>, const noOfInheritedQueries =
tView.contentQueries !== null ? tView.contentQueries[0] : tQueries.length;
const viewLQueries: LQuery<any>[] = new Array(noOfInheritedQueries);
/** // An embedded view has queries propagated from a declaration view at the beginning of the
* A predicate which determines if a given element/directive should be included in the query // TQueries collection and up until a first content query declared in the embedded view. Only
* results. // propagated LQueries are created at this point (LQuery corresponding to declared content
*/ // queries will be instantiated from the content query instructions for each directive).
public predicate: QueryPredicate<T>, for (let i = 0; i < noOfInheritedQueries; i++) {
const tQuery = tQueries.getByIndex(i);
const parentLQuery = this.queries[tQuery.indexInDeclarationView];
viewLQueries[i] = parentLQuery.clone();
}
/** return new LQueries_(viewLQueries);
* Values which have been located. }
* This is what builds up the `QueryList._valuesTree`.
*/
public values: any[],
/** return null;
* 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.
*/ insertView(tView: TView): void { this.dirtyQueriesWithMatches(tView); }
public containerValues: any[]|null) {}
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( constructor(
public parent: LQueries_|null, private shallow: LQuery<any>|null, public predicate: Type<any>|string[], public descendants: boolean, public read: any,
private deep: LQuery<any>|null, public nodeIndex: number = -1) {} public isStatic: boolean) {}
}
track<T>(queryList: QueryList<T>, predicate: Type<T>|string[], descend?: boolean, read?: Type<T>): class TQueries_ implements TQueries {
void { constructor(private queries: TQuery[] = []) {}
if (descend) {
this.deep = createLQuery(this.deep, queryList, predicate, read != null ? read : null); 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 { } 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 { private matchTNodeByLocalNames(tView: TView, tNode: TNode, localNames: string[]): void {
return this.shallow !== null || isContentQueryHost(tNode) ? for (let i = 0; i < localNames.length; i++) {
new LQueries_(this, null, this.deep, tNode.index) : this.matchTNodeWithReadOption(tView, tNode, getIdxOfMatchingSelector(tNode, localNames[i]));
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();
} }
query = query.next;
} }
}
function removeView(query: LQuery<any>| null) { private matchTNodeByTemplateRef(tView: TView, tNode: TNode): void {
while (query) { this.matchTNodeWithReadOption(tView, tNode, tNode.type === TNodeType.Container ? -1 : null);
ngDevMode && assertViewQueryhasPointerToDeclarationContainer(query); }
const containerValues = query.containerValues !; private matchTNodeByType(tView: TView, tNode: TNode, typePredicate: Type<any>): void {
const viewValuesIdx = containerValues.indexOf(query.values); this.matchTNodeWithReadOption(
const removed = containerValues.splice(viewValuesIdx, 1); tView, tNode, locateDirectiveOrProvider(tNode, tView, typePredicate, false, false));
}
// mark a query as dirty only when removed view had matching modes private matchTNodeWithReadOption(tView: TView, tNode: TNode, nodeMatchIdx: number|null): void {
ngDevMode && assertEqual(removed.length, 1, 'removed.length'); if (nodeMatchIdx !== null) {
if (removed[0].length) { const read = this.metadata.read;
query.list.setDirty(); 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>) { private addMatch(tNodeIdx: number, matchIdx: number) {
assertDefined(query.containerValues, 'View queries need to have a pointer to container values.'); 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 { function getIdxOfMatchingSelector(tNode: TNode, selector: string): number|null {
const localNames = tNode.localNames; const localNames = tNode.localNames;
if (localNames) { if (localNames !== null) {
for (let i = 0; i < localNames.length; i += 2) { for (let i = 0; i < localNames.length; i += 2) {
if (localNames[i] === selector) { if (localNames[i] === selector) {
return localNames[i + 1] as number; 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 createResultByTNodeType(tNode: TNode, currentView: LView): any {
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 {
if (tNode.type === TNodeType.Element || tNode.type === TNodeType.ElementContainer) { if (tNode.type === TNodeType.Element || tNode.type === TNodeType.ElementContainer) {
return createElementRef(ViewEngine_ElementRef, tNode, currentView); return createElementRef(ViewEngine_ElementRef, tNode, currentView);
} } else if (tNode.type === TNodeType.Container) {
if (tNode.type === TNodeType.Container) {
return createTemplateRef(ViewEngine_TemplateRef, ViewEngine_ElementRef, tNode, currentView); return createTemplateRef(ViewEngine_TemplateRef, ViewEngine_ElementRef, tNode, currentView);
} }
return null; return null;
} }
function queryByTemplateRef(
templateRefToken: ViewEngine_TemplateRef<any>, tNode: TNode, currentView: LView, function createResultForNode(lView: LView, tNode: TNode, matchingIdx: number, read: any): any {
read: any): any { if (matchingIdx === -1) {
const templateRefResult = (templateRefToken as any)[NG_ELEMENT_ID](); // if read token and / or strategy is not specified, detect it using appropriate tNode type
if (read) { return createResultByTNodeType(tNode, lView);
return templateRefResult ? queryByReadToken(read, tNode, currentView) : null; } 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 { function createSpecialToken(lView: LView, tNode: TNode, read: any): any {
if (read) { if (read === ViewEngine_ElementRef) {
return queryByReadToken(read, tNode, currentView); 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. * 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
* @param query The first query in the linked list * doesn't change).
* @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)
*/ */
function add( function materializeViewResults<T>(lView: LView, tQuery: TQuery, queryIndex: number): (T | null)[] {
query: LQuery<any>| null, tNode: TElementNode | TContainerNode | TElementContainerNode, const lQuery = lView[QUERIES] !.queries ![queryIndex];
insertBeforeContainer: boolean) { if (lQuery.matches === null) {
const lView = getLView(); const tViewData = lView[TVIEW].data;
const tView = lView[TVIEW]; const tQueryMatches = tQuery.matches !;
const result: T|null[] = new Array(tQueryMatches.length / 2);
while (query) { for (let i = 0; i < tQueryMatches.length; i += 2) {
const predicate = query.predicate; const matchedNodeIdx = tQueryMatches[i];
const type = predicate.type as any; if (matchedNodeIdx < 0) {
if (type) { // we at the <ng-template> marker which might have results in views created based on this
let result = null; // <ng-template> - those results will be in separate views though, so here we just leave
if (type === ViewEngine_TemplateRef) { // null as a placeholder
result = queryByTemplateRef(type, tNode, lView, predicate.read); result[i / 2] = null;
} else { } else {
const matchingIdx = locateDirectiveOrProvider(tNode, tView, type, false, false); ngDevMode && assertDataInRange(tViewData, matchedNodeIdx);
if (matchingIdx !== null) { const tNode = tViewData[matchedNodeIdx] as TNode;
result = queryRead(tNode, lView, predicate.read, matchingIdx); 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); // collect matches for views created from this declaration container and inserted into
} // different containers
} else { if (declarationLContainer[MOVED_VIEWS] !== null) {
const selector = predicate.selector !; for (let embeddedLView of declarationLContainer[MOVED_VIEWS] !) {
for (let i = 0; i < selector.length; i++) { const tquery = getTQuery(embeddedLView[TVIEW], childQueryIndex);
const matchingIdx = getIdxOfMatchingSelector(tNode, selector[i]); if (tquery.matches !== null) {
if (matchingIdx !== null) { collectQueryResults(embeddedLView, tquery, childQueryIndex, result);
const result = queryRead(tNode, lView, predicate.read, matchingIdx);
if (result !== null) {
addMatch(query, result, insertBeforeContainer);
} }
} }
} }
} }
query = query.next;
} }
} return result;
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;
} }
/** /**
@ -379,15 +403,24 @@ function createQueryListInLView<T>(
* @codeGenApi * @codeGenApi
*/ */
export function ɵɵqueryRefresh(queryList: QueryList<any>): boolean { export function ɵɵqueryRefresh(queryList: QueryList<any>): boolean {
const queryListImpl = (queryList as any as QueryList_<any>); const lView = getLView();
const creationMode = isCreationMode(); const queryIndex = getCurrentQueryIndex();
// if creation mode and static or update mode and not static setCurrentQueryIndex(queryIndex + 1);
if (queryList.dirty && creationMode === queryListImpl._static) {
queryList.reset(queryListImpl._valuesTree || []); const tQuery = getTQuery(lView[TVIEW], queryIndex);
queryList.notifyOnChanges(); 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 true;
} }
return false; return false;
} }
@ -401,12 +434,8 @@ export function ɵɵqueryRefresh(queryList: QueryList<any>): boolean {
* @codeGenApi * @codeGenApi
*/ */
export function ɵɵstaticViewQuery<T>( export function ɵɵstaticViewQuery<T>(
// TODO(FW-486): "read" should be an AbstractType
predicate: Type<any>| string[], descend: boolean, read: any): void { predicate: Type<any>| string[], descend: boolean, read: any): void {
const lView = getLView(); viewQueryInternal(getLView(), predicate, descend, read, true);
const tView = lView[TVIEW];
viewQueryInternal(lView, tView, predicate, descend, read, true);
tView.staticViewQueries = true;
} }
/** /**
@ -415,41 +444,33 @@ export function ɵɵstaticViewQuery<T>(
* @param predicate The type for which the query will search * @param predicate The type for which the query will search
* @param descend Whether or not to descend into children * @param descend Whether or not to descend into children
* @param read What to save in the query * @param read What to save in the query
* @returns QueryList<T>
* *
* @codeGenApi * @codeGenApi
*/ */
export function ɵɵviewQuery<T>( export function ɵɵviewQuery<T>(predicate: Type<any>| string[], descend: boolean, read: any): void {
// TODO(FW-486): "read" should be an AbstractType viewQueryInternal(getLView(), predicate, descend, read, false);
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);
} }
function viewQueryInternal<T>( function viewQueryInternal<T>(
lView: LView, tView: TView, predicate: Type<any>| string[], descend: boolean, read: any, lView: LView, predicate: Type<any>| string[], descend: boolean, read: any,
isStatic: boolean): QueryList<T> { isStatic: boolean): void {
const tView = lView[TVIEW];
if (tView.firstTemplatePass) { if (tView.firstTemplatePass) {
tView.expandoStartIndex++; createTQuery(tView, new TQueryMetadata_(predicate, descend, read, isStatic), -1);
if (isStatic) {
tView.staticViewQueries = true;
}
} }
const index = getCurrentQueryIndex(); createLQuery<T>(lView);
const queryList: QueryList<T> =
createQueryListInLView<T>(lView, predicate, descend, read, isStatic, -1);
store(index - HEADER_OFFSET, queryList);
setCurrentQueryIndex(index + 1);
return queryList;
} }
/** /**
* 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 * @codeGenApi
*/ */
export function ɵɵloadViewQuery<T>(): T { export function ɵɵloadViewQuery<T>(): QueryList<T> {
const index = getCurrentQueryIndex(); return loadQueryInternal<T>(getLView(), getCurrentQueryIndex());
setCurrentQueryIndex(index + 1);
return loadInternal<T>(getLView(), index - HEADER_OFFSET);
} }
/** /**
@ -465,33 +486,9 @@ export function ɵɵloadViewQuery<T>(): T {
* @codeGenApi * @codeGenApi
*/ */
export function ɵɵcontentQuery<T>( export function ɵɵcontentQuery<T>(
directiveIndex: number, predicate: Type<any>| string[], descend: boolean, directiveIndex: number, predicate: Type<any>| string[], descend: boolean, read: any): void {
// TODO(FW-486): "read" should be an AbstractType contentQueryInternal(
read: any): QueryList<T> { getLView(), predicate, descend, read, false, getPreviousOrParentTNode(), directiveIndex);
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;
} }
/** /**
@ -507,29 +504,65 @@ function contentQueryInternal<T>(
* @codeGenApi * @codeGenApi
*/ */
export function ɵɵstaticContentQuery<T>( export function ɵɵstaticContentQuery<T>(
directiveIndex: number, predicate: Type<any>| string[], descend: boolean, directiveIndex: number, predicate: Type<any>| string[], descend: boolean, read: any): void {
// TODO(FW-486): "read" should be an AbstractType contentQueryInternal(
read: any): void { getLView(), predicate, descend, read, true, getPreviousOrParentTNode(), directiveIndex);
const lView = getLView(); }
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 tView = lView[TVIEW];
const tNode = getPreviousOrParentTNode(); if (tView.firstTemplatePass) {
contentQueryInternal(lView, tView, directiveIndex, predicate, descend, read, true, tNode.index); createTQuery(tView, new TQueryMetadata_(predicate, descend, read, isStatic), tNode.index);
tView.staticContentQueries = true; saveContentQueryAndDirectiveIndex(tView, directiveIndex);
if (isStatic) {
tView.staticContentQueries = true;
}
}
createLQuery<T>(lView);
} }
/** /**
* Loads a QueryList corresponding to the current content query.
* *
* @codeGenApi * @codeGenApi
*/ */
export function ɵɵloadContentQuery<T>(): QueryList<T> { export function ɵɵloadContentQuery<T>(): QueryList<T> {
const lView = getLView(); return loadQueryInternal<T>(getLView(), getCurrentQueryIndex());
ngDevMode && }
assertDefined(
lView[CONTENT_QUERIES], 'Content QueryList array should be defined if reading a query.'); function loadQueryInternal<T>(lView: LView, queryIndex: number): QueryList<T> {
ngDevMode &&
const index = getCurrentQueryIndex(); assertDefined(lView[QUERIES], 'LQueries should be defined when trying to load a query');
ngDevMode && assertDataInRange(lView[CONTENT_QUERIES] !, index); ngDevMode && assertDataInRange(lView[QUERIES] !.queries, queryIndex);
return lView[QUERIES] !.queries[queryIndex].queryList;
setCurrentQueryIndex(index + 1); }
return lView[CONTENT_QUERIES] ![index];
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 {Renderer2} from '../render/api';
import {assertDefined, assertGreaterThan, assertLessThan} from '../util/assert'; import {assertDefined, assertGreaterThan, assertLessThan} from '../util/assert';
import {assertLContainer} from './assert';
import {NodeInjector, getParentInjectorLocation} from './di'; import {NodeInjector, getParentInjectorLocation} from './di';
import {addToViewTree, createEmbeddedViewAndNode, createLContainer, renderEmbeddedTemplate} from './instructions/shared'; import {addToViewTree, createEmbeddedViewAndNode, createLContainer, renderEmbeddedTemplate} from './instructions/shared';
import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer, VIEW_REFS} from './interfaces/container'; import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer, VIEW_REFS} from './interfaces/container';
import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeType, TViewNode} from './interfaces/node'; import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeType, TViewNode} from './interfaces/node';
import {RComment, RElement, isProceduralRenderer} from './interfaces/renderer'; import {RComment, RElement, isProceduralRenderer} from './interfaces/renderer';
import {isComponent, isLContainer, isLView, isRootView} from './interfaces/type_checks'; 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 {assertNodeOfPossibleTypes} from './node_assert';
import {addRemoveViewFromContainer, appendChild, detachView, getBeforeNodeForView, insertView, nativeInsertBefore, nativeNextSibling, nativeParentNode, removeView} from './node_manipulation'; import {addRemoveViewFromContainer, appendChild, detachView, getBeforeNodeForView, insertView, nativeInsertBefore, nativeNextSibling, nativeParentNode, removeView} from './node_manipulation';
import {getParentInjectorTNode} from './node_util'; import {getParentInjectorTNode} from './node_util';
@ -66,9 +69,8 @@ export function createElementRef(
} }
let R3TemplateRef: { let R3TemplateRef: {
new ( new (_declarationParentView: LView, hostTNode: TContainerNode, elementRef: ViewEngine_ElementRef):
_declarationParentView: LView, elementRef: ViewEngine_ElementRef, _tView: TView, ViewEngine_TemplateRef<any>
_hostLContainer: LContainer, _injectorIndex: number): ViewEngine_TemplateRef<any>
}; };
/** /**
@ -88,9 +90,9 @@ export function injectTemplateRef<T>(
* *
* @param TemplateRefToken The TemplateRef type * @param TemplateRefToken The TemplateRef type
* @param ElementRefToken The ElementRef 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 * @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>( export function createTemplateRef<T>(
TemplateRefToken: typeof ViewEngine_TemplateRef, ElementRefToken: typeof ViewEngine_ElementRef, 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 // TODO: Fix class name, should be TemplateRef, but there appears to be a rollup bug
R3TemplateRef = class TemplateRef_<T> extends TemplateRefToken<T> { R3TemplateRef = class TemplateRef_<T> extends TemplateRefToken<T> {
constructor( constructor(
private _declarationParentView: LView, readonly elementRef: ViewEngine_ElementRef, private _declarationView: LView, private _declarationTContainer: TContainerNode,
private _tView: TView, private _hostLContainer: LContainer, readonly elementRef: ViewEngine_ElementRef) {
private _injectorIndex: number) {
super(); super();
} }
createEmbeddedView(context: T, container?: LContainer, index?: number): createEmbeddedView(context: T): viewEngine_EmbeddedViewRef<T> {
viewEngine_EmbeddedViewRef<T> { const embeddedTView = this._declarationTContainer.tViews as TView;
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();
}
const lView = createEmbeddedViewAndNode( const lView = createEmbeddedViewAndNode(
this._tView, context, this._declarationParentView, this._hostLContainer[QUERIES], embeddedTView, context, this._declarationView,
this._injectorIndex); this._declarationTContainer.injectorIndex);
if (container) {
insertView(lView, container, index !); 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); const viewRef = new ViewRef(lView, context, -1);
viewRef._tViewNode = lView[T_HOST] as TViewNode; viewRef._tViewNode = lView[T_HOST] as TViewNode;
return viewRef; return viewRef;
@ -128,11 +130,10 @@ export function createTemplateRef<T>(
} }
if (hostTNode.type === TNodeType.Container) { if (hostTNode.type === TNodeType.Container) {
const hostContainer: LContainer = hostView[hostTNode.index];
ngDevMode && assertDefined(hostTNode.tViews, 'TView must be allocated'); ngDevMode && assertDefined(hostTNode.tViews, 'TView must be allocated');
return new R3TemplateRef( return new R3TemplateRef(
hostView, createElementRef(ElementRefToken, hostTNode, hostView), hostTNode.tViews as TView, hostView, hostTNode as TContainerNode,
hostContainer, hostTNode.injectorIndex); createElementRef(ElementRefToken, hostTNode, hostView));
} else { } else {
return null; return null;
} }
@ -218,12 +219,8 @@ export function createContainerRef(
createEmbeddedView<C>(templateRef: ViewEngine_TemplateRef<C>, context?: C, index?: number): createEmbeddedView<C>(templateRef: ViewEngine_TemplateRef<C>, context?: C, index?: number):
viewEngine_EmbeddedViewRef<C> { viewEngine_EmbeddedViewRef<C> {
this.allocateContainerIfNeeded(); const viewRef = templateRef.createEmbeddedView(context || <any>{});
const adjustedIdx = this._adjustIndex(index); this.insert(viewRef, 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);
return viewRef; return viewRef;
} }

View File

@ -7,7 +7,7 @@
*/ */
import {CommonModule} from '@angular/common'; 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 {TestBed} from '@angular/core/testing';
import {By} from '@angular/platform-browser'; import {By} from '@angular/platform-browser';
import {expect} from '@angular/platform-browser/testing/src/matchers'; import {expect} from '@angular/platform-browser/testing/src/matchers';
@ -786,6 +786,56 @@ describe('query logic', () => {
expect(queryList.last.nativeElement.id).toBe('c'); 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 // 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', 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-7vvo9j?file=src%2Fapp%2Fapp.component.ts
// https://stackblitz.com/edit/angular-xzwp6n // 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',
.it('should report results when the same TemplateRef is inserted into different ViewContainerRefs', () => {
() => { @Component({
@Component({ selector: 'test-comp',
selector: 'test-comp', template: `
template: `
<ng-template #tpl let-idx="idx" let-container_idx="container_idx"> <ng-template #tpl let-idx="idx" let-container_idx="container_idx">
<div #foo [id]="'foo_' + container_idx + '_' + idx"></div> <div #foo [id]="'foo_' + container_idx + '_' + idx"></div>
</ng-template> </ng-template>
@ -877,44 +926,44 @@ describe('query logic', () => {
<ng-template vc #vi0="vc"></ng-template> <ng-template vc #vi0="vc"></ng-template>
<ng-template vc #vi1="vc"></ng-template> <ng-template vc #vi1="vc"></ng-template>
`, `,
}) })
class TestComponent { class TestComponent {
@ViewChild('tpl', {static: false}) tpl !: TemplateRef<any>; @ViewChild('tpl', {static: false}) tpl !: TemplateRef<any>;
@ViewChild('vi0', {static: false}) vi0 !: ViewContainerManipulatorDirective; @ViewChild('vi0', {static: false}) vi0 !: ViewContainerManipulatorDirective;
@ViewChild('vi1', {static: false}) vi1 !: ViewContainerManipulatorDirective; @ViewChild('vi1', {static: false}) vi1 !: ViewContainerManipulatorDirective;
@ViewChildren('foo') query !: QueryList<any>; @ViewChildren('foo') query !: QueryList<any>;
} }
TestBed.configureTestingModule( TestBed.configureTestingModule(
{declarations: [ViewContainerManipulatorDirective, TestComponent]}); {declarations: [ViewContainerManipulatorDirective, TestComponent]});
const fixture = TestBed.createComponent(TestComponent); const fixture = TestBed.createComponent(TestComponent);
fixture.detectChanges(); fixture.detectChanges();
const queryList = fixture.componentInstance.query; const queryList = fixture.componentInstance.query;
const {tpl, vi0, vi1} = fixture.componentInstance; 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); vi0.insertTpl(tpl !, {idx: 0, container_idx: 0}, 0);
vi1.insertTpl(tpl !, {idx: 0, container_idx: 1}, 0); vi1.insertTpl(tpl !, {idx: 0, container_idx: 1}, 0);
fixture.detectChanges(); fixture.detectChanges();
expect(queryList.length).toBe(2); expect(queryList.length).toBe(2);
let qListArr = queryList.toArray(); let qListArr = queryList.toArray();
expect(qListArr[0].nativeElement.getAttribute('id')).toBe('foo_1_0'); expect(qListArr[0].nativeElement.getAttribute('id')).toBe('foo_0_0');
expect(qListArr[1].nativeElement.getAttribute('id')).toBe('foo_0_0'); expect(qListArr[1].nativeElement.getAttribute('id')).toBe('foo_1_0');
vi0.remove(); vi0.remove();
fixture.detectChanges(); fixture.detectChanges();
expect(queryList.length).toBe(1); expect(queryList.length).toBe(1);
qListArr = queryList.toArray(); qListArr = queryList.toArray();
expect(qListArr[0].nativeElement.getAttribute('id')).toBe('foo_1_0'); expect(qListArr[0].nativeElement.getAttribute('id')).toBe('foo_1_0');
vi1.remove(); vi1.remove();
fixture.detectChanges(); fixture.detectChanges();
expect(queryList.length).toBe(0); expect(queryList.length).toBe(0);
}); });
// https://stackblitz.com/edit/angular-wpd6gv?file=src%2Fapp%2Fapp.component.ts // https://stackblitz.com/edit/angular-wpd6gv?file=src%2Fapp%2Fapp.component.ts
it('should report results from views inserted in a lifecycle hook', () => { 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) { function initWithTemplate(compType: Type<any>, template: string) {

View File

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

View File

@ -38,6 +38,9 @@
{ {
"name": "ChangeDetectionStrategy" "name": "ChangeDetectionStrategy"
}, },
{
"name": "DECLARATION_LCONTAINER"
},
{ {
"name": "DECLARATION_VIEW" "name": "DECLARATION_VIEW"
}, },
@ -116,6 +119,9 @@
{ {
"name": "MONKEY_PATCH_KEY_NAME" "name": "MONKEY_PATCH_KEY_NAME"
}, },
{
"name": "MOVED_VIEWS"
},
{ {
"name": "NATIVE" "name": "NATIVE"
}, },
@ -437,9 +443,6 @@
{ {
"name": "addRemoveViewFromContainer" "name": "addRemoveViewFromContainer"
}, },
{
"name": "addTContainerToQueries"
},
{ {
"name": "addToViewTree" "name": "addToViewTree"
}, },
@ -602,6 +605,9 @@
{ {
"name": "destroyViewTree" "name": "destroyViewTree"
}, },
{
"name": "detachMovedView"
},
{ {
"name": "detachView" "name": "detachView"
}, },
@ -1367,6 +1373,9 @@
{ {
"name": "trackByIdentity" "name": "trackByIdentity"
}, },
{
"name": "trackMovedView"
},
{ {
"name": "unwrapRNode" "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', () => { describe('queryList', () => {
it('should be destroyed when the containing view is destroyed', () => { it('should be destroyed when the containing view is destroyed', () => {
let queryInstance: QueryList<any>; 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 ɵɵ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; 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 ɵɵloadContentQuery<T>(): QueryList<T>;
export declare function ɵɵloadViewQuery<T>(): T; export declare function ɵɵloadViewQuery<T>(): QueryList<T>;
export declare function ɵɵnamespaceHTML(): void; 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 ɵɵ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>; export declare const PACKAGE_ROOT_URL: InjectionToken<string>;