perf(ivy): match query results on the TView level (#31489)
PR Close #31489
This commit is contained in:
parent
9c954ebc62
commit
d52ae7cbab
|
@ -119,7 +119,7 @@ export class QueryList<T>/* implements Iterable<T> */ {
|
|||
* on change detection, it will not notify of changes to the queries, unless a new change
|
||||
* occurs.
|
||||
*
|
||||
* @param resultsTree The results tree to store
|
||||
* @param resultsTree The query results to store
|
||||
*/
|
||||
reset(resultsTree: Array<T|any[]>): void {
|
||||
this._results = flatten(resultsTree);
|
||||
|
|
|
@ -69,3 +69,7 @@ export function assertLView(value: any) {
|
|||
assertDefined(value, 'LView must be defined');
|
||||
assertEqual(isLView(value), true, 'Expecting LView');
|
||||
}
|
||||
|
||||
export function assertFirstTemplatePass(tView: TView, errMessage: string) {
|
||||
assertEqual(tView.firstTemplatePass, true, errMessage);
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
import {Type} from '../core';
|
||||
import {Injector} from '../di/injector';
|
||||
import {Sanitizer} from '../sanitization/security';
|
||||
import {assertDataInRange, assertEqual} from '../util/assert';
|
||||
import {assertDataInRange} from '../util/assert';
|
||||
|
||||
import {assertComponentType} from './assert';
|
||||
import {getComponentDef} from './definition';
|
||||
|
|
|
@ -12,13 +12,14 @@ import {executePreOrderHooks, registerPostOrderHooks} from '../hooks';
|
|||
import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer} from '../interfaces/container';
|
||||
import {ComponentTemplate} from '../interfaces/definition';
|
||||
import {LocalRefExtractor, TAttributes, TContainerNode, TNode, TNodeType} from '../interfaces/node';
|
||||
import {BINDING_INDEX, HEADER_OFFSET, LView, QUERIES, RENDERER, TVIEW, T_HOST} from '../interfaces/view';
|
||||
import {BINDING_INDEX, HEADER_OFFSET, LView, RENDERER, TVIEW, T_HOST} from '../interfaces/view';
|
||||
import {assertNodeType} from '../node_assert';
|
||||
import {appendChild, removeView} from '../node_manipulation';
|
||||
import {getCheckNoChangesMode, getIsParent, getLView, getPreviousOrParentTNode, setIsNotParent, setPreviousOrParentTNode} from '../state';
|
||||
import {getNativeByTNode, loadInternal} from '../util/view_utils';
|
||||
|
||||
import {addToViewTree, createDirectivesAndLocals, createLContainer, createTView, getOrCreateTNode} from './shared';
|
||||
import {addToViewTree, createDirectivesAndLocals, createLContainer, createTView, getOrCreateTNode, resolveDirectives} from './shared';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
|
@ -39,7 +40,6 @@ export function ɵɵcontainer(index: number): void {
|
|||
if (lView[TVIEW].firstTemplatePass) {
|
||||
tNode.tViews = [];
|
||||
}
|
||||
addTContainerToQueries(lView, tNode);
|
||||
setIsNotParent();
|
||||
}
|
||||
|
||||
|
@ -72,12 +72,18 @@ export function ɵɵtemplate(
|
|||
// TODO: consider a separate node type for templates
|
||||
const tContainerNode = containerInternal(lView, index, tagName || null, attrs || null);
|
||||
if (tView.firstTemplatePass) {
|
||||
tContainerNode.tViews = createTView(
|
||||
ngDevMode && ngDevMode.firstTemplatePass++;
|
||||
resolveDirectives(tView, lView, tContainerNode, localRefs || null);
|
||||
|
||||
const embeddedTView = tContainerNode.tViews = createTView(
|
||||
-1, templateFn, consts, vars, tView.directiveRegistry, tView.pipeRegistry, null, null);
|
||||
if (tView.queries !== null) {
|
||||
tView.queries.template(tView, tContainerNode);
|
||||
embeddedTView.queries = tView.queries.embeddedTView(tContainerNode);
|
||||
}
|
||||
}
|
||||
|
||||
createDirectivesAndLocals(tView, lView, tContainerNode, localRefs, localRefExtractor);
|
||||
addTContainerToQueries(lView, tContainerNode);
|
||||
createDirectivesAndLocals(tView, lView, tContainerNode, localRefExtractor);
|
||||
attachPatchData(getNativeByTNode(tContainerNode, lView), lView);
|
||||
registerPostOrderHooks(tView, tContainerNode);
|
||||
setIsNotParent();
|
||||
|
@ -133,32 +139,6 @@ export function ɵɵcontainerRefreshEnd(): void {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reporting a TContainer node queries is a 2-step process as we need to:
|
||||
* - check if the container node itself is matching (query might match a <ng-template> node);
|
||||
* - prepare room for nodes from views that might be created based on the TemplateRef linked to this
|
||||
* container.
|
||||
*
|
||||
* Those 2 operations need to happen in the specific order (match the container node itself, then
|
||||
* prepare space for nodes from views).
|
||||
*/
|
||||
function addTContainerToQueries(lView: LView, tContainerNode: TContainerNode): void {
|
||||
const queries = lView[QUERIES];
|
||||
if (queries) {
|
||||
const lContainer = lView[tContainerNode.index];
|
||||
if (lContainer[QUERIES]) {
|
||||
// Query container should only exist if it was created through a dynamic view
|
||||
// in a directive constructor. In this case, we must splice the template
|
||||
// matches in before the view matches to ensure query results in embedded views
|
||||
// don't clobber query results on the template node itself.
|
||||
queries.insertNodeBeforeViews(tContainerNode);
|
||||
} else {
|
||||
queries.addNode(tContainerNode);
|
||||
lContainer[QUERIES] = queries.container();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function containerInternal(
|
||||
lView: LView, nodeIndex: number, tagName: string | null,
|
||||
attrs: TAttributes | null): TContainerNode {
|
||||
|
|
|
@ -5,13 +5,15 @@
|
|||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {assertDataInRange, assertDefined, assertEqual} from '../../util/assert';
|
||||
import {assertHasParent} from '../assert';
|
||||
import {attachPatchData} from '../context_discovery';
|
||||
import {registerPostOrderHooks} from '../hooks';
|
||||
import {PropertyAliases, TAttributes, TNode, TNodeFlags, TNodeType} from '../interfaces/node';
|
||||
import {TAttributes, TNodeFlags, TNodeType} from '../interfaces/node';
|
||||
import {RElement} from '../interfaces/renderer';
|
||||
import {BINDING_INDEX, HEADER_OFFSET, LView, QUERIES, RENDERER, TVIEW, T_HOST} from '../interfaces/view';
|
||||
import {isContentQueryHost} from '../interfaces/type_checks';
|
||||
import {BINDING_INDEX, HEADER_OFFSET, LView, RENDERER, TVIEW, T_HOST} from '../interfaces/view';
|
||||
import {assertNodeType} from '../node_assert';
|
||||
import {appendChild} from '../node_manipulation';
|
||||
import {applyOnCreateInstructions} from '../node_util';
|
||||
|
@ -22,7 +24,7 @@ import {getInitialStylingValue, hasClassInput, hasStyleInput} from '../styling_n
|
|||
import {setUpAttributes} from '../util/attrs_utils';
|
||||
import {getNativeByTNode, getTNode} from '../util/view_utils';
|
||||
|
||||
import {createDirectivesAndLocals, elementCreate, executeContentQueries, getOrCreateTNode, initializeTNodeInputs, renderInitialStyling, setInputsForProperty} from './shared';
|
||||
import {createDirectivesAndLocals, elementCreate, executeContentQueries, getOrCreateTNode, initializeTNodeInputs, renderInitialStyling, resolveDirectives, setInputsForProperty} from './shared';
|
||||
|
||||
|
||||
|
||||
|
@ -66,7 +68,6 @@ export function ɵɵelementStart(
|
|||
renderInitialStyling(renderer, native, tNode);
|
||||
|
||||
appendChild(native, tNode, lView);
|
||||
createDirectivesAndLocals(tView, lView, tNode, localRefs);
|
||||
|
||||
// any immediate children of a component or template container must be pre-emptively
|
||||
// monkey-patched with the component view data so that the element can be inspected
|
||||
|
@ -81,6 +82,9 @@ export function ɵɵelementStart(
|
|||
// static class values as well. (Note that this will be fixed once map-based `[style]`
|
||||
// and `[class]` bindings work for multiple directives.)
|
||||
if (tView.firstTemplatePass) {
|
||||
ngDevMode && ngDevMode.firstTemplatePass++;
|
||||
resolveDirectives(tView, lView, tNode, localRefs || null);
|
||||
|
||||
const inputData = initializeTNodeInputs(tNode);
|
||||
if (inputData && inputData.hasOwnProperty('class')) {
|
||||
tNode.flags |= TNodeFlags.hasClassInput;
|
||||
|
@ -89,13 +93,13 @@ export function ɵɵelementStart(
|
|||
if (inputData && inputData.hasOwnProperty('style')) {
|
||||
tNode.flags |= TNodeFlags.hasStyleInput;
|
||||
}
|
||||
|
||||
if (tView.queries !== null) {
|
||||
tView.queries.elementStart(tView, tNode);
|
||||
}
|
||||
}
|
||||
|
||||
const currentQueries = lView[QUERIES];
|
||||
if (currentQueries) {
|
||||
currentQueries.addNode(tNode);
|
||||
lView[QUERIES] = currentQueries.clone(tNode);
|
||||
}
|
||||
createDirectivesAndLocals(tView, lView, tNode);
|
||||
executeContentQueries(tView, tNode, lView);
|
||||
}
|
||||
|
||||
|
@ -123,15 +127,16 @@ export function ɵɵelementEnd(): void {
|
|||
|
||||
ngDevMode && assertNodeType(tNode, TNodeType.Element);
|
||||
const lView = getLView();
|
||||
const currentQueries = lView[QUERIES];
|
||||
// Go back up to parent queries only if queries have been cloned on this element.
|
||||
if (currentQueries && tNode.index === currentQueries.nodeIndex) {
|
||||
lView[QUERIES] = currentQueries.parent;
|
||||
}
|
||||
const tView = lView[TVIEW];
|
||||
|
||||
registerPostOrderHooks(lView[TVIEW], tNode);
|
||||
registerPostOrderHooks(tView, previousOrParentTNode);
|
||||
decreaseElementDepthCount();
|
||||
|
||||
if (tView.firstTemplatePass && tView.queries !== null &&
|
||||
isContentQueryHost(previousOrParentTNode)) {
|
||||
tView.queries !.elementEnd(previousOrParentTNode);
|
||||
}
|
||||
|
||||
if (hasClassInput(tNode) && tNode.classes) {
|
||||
setDirectiveStylingInput(tNode.classes, lView, tNode.inputs !['class']);
|
||||
}
|
||||
|
|
|
@ -10,14 +10,15 @@ import {assertHasParent} from '../assert';
|
|||
import {attachPatchData} from '../context_discovery';
|
||||
import {registerPostOrderHooks} from '../hooks';
|
||||
import {TAttributes, TNodeType} from '../interfaces/node';
|
||||
import {BINDING_INDEX, HEADER_OFFSET, QUERIES, RENDERER, TVIEW, T_HOST} from '../interfaces/view';
|
||||
import {isContentQueryHost} from '../interfaces/type_checks';
|
||||
import {BINDING_INDEX, HEADER_OFFSET, RENDERER, TVIEW, T_HOST} from '../interfaces/view';
|
||||
import {assertNodeType} from '../node_assert';
|
||||
import {appendChild} from '../node_manipulation';
|
||||
import {applyOnCreateInstructions} from '../node_util';
|
||||
import {getIsParent, getLView, getPreviousOrParentTNode, setIsNotParent, setPreviousOrParentTNode} from '../state';
|
||||
import {registerInitialStylingOnTNode} from '../styling_next/instructions';
|
||||
|
||||
import {createDirectivesAndLocals, executeContentQueries, getOrCreateTNode} from './shared';
|
||||
import {createDirectivesAndLocals, executeContentQueries, getOrCreateTNode, resolveDirectives} from './shared';
|
||||
|
||||
|
||||
|
||||
|
@ -61,14 +62,17 @@ export function ɵɵelementContainerStart(
|
|||
}
|
||||
|
||||
appendChild(native, tNode, lView);
|
||||
createDirectivesAndLocals(tView, lView, tNode, localRefs);
|
||||
attachPatchData(native, lView);
|
||||
|
||||
const currentQueries = lView[QUERIES];
|
||||
if (currentQueries) {
|
||||
currentQueries.addNode(tNode);
|
||||
lView[QUERIES] = currentQueries.clone(tNode);
|
||||
if (tView.firstTemplatePass) {
|
||||
ngDevMode && ngDevMode.firstTemplatePass++;
|
||||
resolveDirectives(tView, lView, tNode, localRefs || null);
|
||||
if (tView.queries) {
|
||||
tView.queries.elementStart(tView, tNode);
|
||||
}
|
||||
}
|
||||
|
||||
createDirectivesAndLocals(tView, lView, tNode);
|
||||
attachPatchData(native, lView);
|
||||
executeContentQueries(tView, tNode, lView);
|
||||
}
|
||||
|
||||
|
@ -90,17 +94,17 @@ export function ɵɵelementContainerEnd(): void {
|
|||
}
|
||||
|
||||
ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.ElementContainer);
|
||||
const currentQueries = lView[QUERIES];
|
||||
// Go back up to parent queries only if queries have been cloned on this element.
|
||||
if (currentQueries && previousOrParentTNode.index === currentQueries.nodeIndex) {
|
||||
lView[QUERIES] = currentQueries.parent;
|
||||
}
|
||||
|
||||
// this is required for all host-level styling-related instructions to run
|
||||
// in the correct order
|
||||
previousOrParentTNode.onElementCreationFns && applyOnCreateInstructions(previousOrParentTNode);
|
||||
|
||||
registerPostOrderHooks(tView, previousOrParentTNode);
|
||||
|
||||
if (tView.firstTemplatePass && tView.queries !== null &&
|
||||
isContentQueryHost(previousOrParentTNode)) {
|
||||
tView.queries.elementEnd(previousOrParentTNode);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -49,10 +49,6 @@ export function ɵɵembeddedViewStart(
|
|||
getOrCreateEmbeddedTView(viewBlockId, consts, vars, containerTNode as TContainerNode), null,
|
||||
LViewFlags.CheckAlways, null, null);
|
||||
|
||||
if (lContainer[QUERIES]) {
|
||||
viewToRender[QUERIES] = lContainer[QUERIES] !.createView();
|
||||
}
|
||||
|
||||
const tParentNode = getIsParent() ? previousOrParentTNode :
|
||||
previousOrParentTNode && previousOrParentTNode.parent;
|
||||
assignTViewNodeToLView(viewToRender[TVIEW], tParentNode, viewBlockId, viewToRender);
|
||||
|
|
|
@ -10,15 +10,16 @@ import {AttributeMarker, ComponentTemplate} from '..';
|
|||
import {SchemaMetadata} from '../../core';
|
||||
import {assertDefined} from '../../util/assert';
|
||||
import {createNamedArrayType} from '../../util/named_array_type';
|
||||
import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer, NATIVE} from '../interfaces/container';
|
||||
import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer, MOVED_VIEWS, NATIVE} from '../interfaces/container';
|
||||
import {DirectiveDefList, PipeDefList, ViewQueriesFunction} from '../interfaces/definition';
|
||||
import {COMMENT_MARKER, ELEMENT_MARKER, I18nMutateOpCode, I18nMutateOpCodes, I18nUpdateOpCode, I18nUpdateOpCodes, TIcu} from '../interfaces/i18n';
|
||||
import {PropertyAliases, TContainerNode, TElementNode, TNode as ITNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TViewNode} from '../interfaces/node';
|
||||
import {SelectorFlags} from '../interfaces/projection';
|
||||
import {LQueries} from '../interfaces/query';
|
||||
import {TQueries} from '../interfaces/query';
|
||||
import {RComment, RElement, RNode} from '../interfaces/renderer';
|
||||
import {StylingContext} from '../interfaces/styling';
|
||||
import {BINDING_INDEX, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTENT_QUERIES, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, HookData, INJECTOR, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, SANITIZER, TData, TVIEW, TView as ITView, TView, T_HOST} from '../interfaces/view';
|
||||
|
||||
import {BINDING_INDEX, CHILD_HEAD, CHILD_TAIL, CLEANUP, CONTEXT, DECLARATION_VIEW, ExpandoInstructions, FLAGS, HEADER_OFFSET, HOST, HookData, INJECTOR, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, RENDERER_FACTORY, SANITIZER, TData, TVIEW, TView as ITView, TView, T_HOST} from '../interfaces/view';
|
||||
import {TStylingContext} from '../styling_next/interfaces';
|
||||
import {DebugStyling as DebugNewStyling, NodeStylingDebug} from '../styling_next/styling_debug';
|
||||
import {isStylingContext} from '../styling_next/util';
|
||||
|
@ -78,11 +79,11 @@ export const TViewConstructor = class TView implements ITView {
|
|||
public id: number, //
|
||||
public blueprint: LView, //
|
||||
public template: ComponentTemplate<{}>|null, //
|
||||
public queries: TQueries|null, //
|
||||
public viewQuery: ViewQueriesFunction<{}>|null, //
|
||||
public node: TViewNode|TElementNode|null, //
|
||||
public data: TData, //
|
||||
public bindingStartIndex: number, //
|
||||
public viewQueryStartIndex: number, //
|
||||
public expandoStartIndex: number, //
|
||||
public expandoInstructions: ExpandoInstructions|null, //
|
||||
public firstTemplatePass: boolean, //
|
||||
|
@ -287,8 +288,7 @@ export class LViewDebug {
|
|||
next: toDebug(this._raw_lView[NEXT]),
|
||||
childTail: toDebug(this._raw_lView[CHILD_TAIL]),
|
||||
declarationView: toDebug(this._raw_lView[DECLARATION_VIEW]),
|
||||
contentQueries: this._raw_lView[CONTENT_QUERIES],
|
||||
queries: this._raw_lView[QUERIES],
|
||||
queries: null,
|
||||
tHost: this._raw_lView[T_HOST],
|
||||
bindingIndex: this._raw_lView[BINDING_INDEX],
|
||||
};
|
||||
|
@ -361,7 +361,7 @@ export class LContainerDebug {
|
|||
.map(toDebug as(l: LView) => LViewDebug);
|
||||
}
|
||||
get parent(): LViewDebug|LContainerDebug|null { return toDebug(this._raw_lContainer[PARENT]); }
|
||||
get queries(): LQueries|null { return this._raw_lContainer[QUERIES]; }
|
||||
get movedViews(): LView[]|null { return this._raw_lContainer[MOVED_VIEWS]; }
|
||||
get host(): RElement|RComment|StylingContext|LView { return this._raw_lContainer[HOST]; }
|
||||
get native(): RComment { return this._raw_lContainer[NATIVE]; }
|
||||
get __other__() {
|
||||
|
|
|
@ -23,7 +23,6 @@ import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer} from '../interfaces/c
|
|||
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, FactoryFn, PipeDefListOrFactory, RenderFlags, ViewQueriesFunction} from '../interfaces/definition';
|
||||
import {INJECTOR_BLOOM_PARENT_SIZE, NodeInjectorFactory} from '../interfaces/injector';
|
||||
import {AttributeMarker, InitialInputData, InitialInputs, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeProviderIndexes, TNodeType, TProjectionNode, TViewNode} from '../interfaces/node';
|
||||
import {LQueries} from '../interfaces/query';
|
||||
import {RComment, RElement, RText, Renderer3, RendererFactory3, isProceduralRenderer} from '../interfaces/renderer';
|
||||
import {SanitizerFn} from '../interfaces/sanitization';
|
||||
import {StylingContext} from '../interfaces/styling';
|
||||
|
@ -157,16 +156,20 @@ export function setHostBindings(tView: TView, viewData: LView): void {
|
|||
}
|
||||
}
|
||||
|
||||
/** Refreshes content queries for all directives in the given view. */
|
||||
/** Refreshes all content queries declared by directives in a given view */
|
||||
function refreshContentQueries(tView: TView, lView: LView): void {
|
||||
if (tView.contentQueries != null) {
|
||||
setCurrentQueryIndex(0);
|
||||
for (let i = 0; i < tView.contentQueries.length; i++) {
|
||||
const directiveDefIdx = tView.contentQueries[i];
|
||||
const directiveDef = tView.data[directiveDefIdx] as DirectiveDef<any>;
|
||||
ngDevMode &&
|
||||
assertDefined(directiveDef.contentQueries, 'contentQueries function should be defined');
|
||||
directiveDef.contentQueries !(RenderFlags.Update, lView[directiveDefIdx], directiveDefIdx);
|
||||
const contentQueries = tView.contentQueries;
|
||||
if (contentQueries !== null) {
|
||||
for (let i = 0; i < contentQueries.length; i += 2) {
|
||||
const queryStartIdx = contentQueries[i];
|
||||
const directiveDefIdx = contentQueries[i + 1];
|
||||
if (directiveDefIdx !== -1) {
|
||||
const directiveDef = tView.data[directiveDefIdx] as DirectiveDef<any>;
|
||||
ngDevMode &&
|
||||
assertDefined(directiveDef.contentQueries, 'contentQueries function should be defined');
|
||||
setCurrentQueryIndex(queryStartIdx);
|
||||
directiveDef.contentQueries !(RenderFlags.Update, lView[directiveDefIdx], directiveDefIdx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -361,8 +364,7 @@ export function allocExpando(view: LView, numSlotsToAlloc: number) {
|
|||
* Such lViewNode will then be renderer with renderEmbeddedTemplate() (see below).
|
||||
*/
|
||||
export function createEmbeddedViewAndNode<T>(
|
||||
tView: TView, context: T, declarationView: LView, queries: LQueries | null,
|
||||
injectorIndex: number): LView {
|
||||
tView: TView, context: T, declarationView: LView, injectorIndex: number): LView {
|
||||
const _isParent = getIsParent();
|
||||
const _previousOrParentTNode = getPreviousOrParentTNode();
|
||||
setPreviousOrParentTNode(null !, true);
|
||||
|
@ -370,9 +372,6 @@ export function createEmbeddedViewAndNode<T>(
|
|||
const lView = createLView(declarationView, tView, context, LViewFlags.CheckAlways, null, null);
|
||||
lView[DECLARATION_VIEW] = declarationView;
|
||||
|
||||
if (queries) {
|
||||
lView[QUERIES] = queries.createView();
|
||||
}
|
||||
assignTViewNodeToLView(tView, null, -1, lView);
|
||||
|
||||
if (tView.firstTemplatePass) {
|
||||
|
@ -513,14 +512,8 @@ export function executeContentQueries(tView: TView, tNode: TNode, lView: LView)
|
|||
*/
|
||||
export function createDirectivesAndLocals(
|
||||
tView: TView, lView: LView, tNode: TElementNode | TContainerNode | TElementContainerNode,
|
||||
localRefs: string[] | null | undefined,
|
||||
localRefExtractor: LocalRefExtractor = getNativeByTNode) {
|
||||
if (!getBindingsEnabled()) return;
|
||||
if (tView.firstTemplatePass) {
|
||||
ngDevMode && ngDevMode.firstTemplatePass++;
|
||||
resolveDirectives(
|
||||
tView, lView, findDirectiveMatches(tView, lView, tNode), tNode, localRefs || null);
|
||||
}
|
||||
instantiateAllDirectives(tView, lView, tNode);
|
||||
invokeDirectivesHostBindings(tView, lView, tNode);
|
||||
saveResolvedLocalsInData(lView, tNode, localRefExtractor);
|
||||
|
@ -588,11 +581,11 @@ export function createTView(
|
|||
viewIndex, // id: number,
|
||||
blueprint, // blueprint: LView,
|
||||
templateFn, // template: ComponentTemplate<{}>|null,
|
||||
null, // queries: TQueries|null
|
||||
viewQuery, // viewQuery: ViewQueriesFunction<{}>|null,
|
||||
null !, // node: TViewNode|TElementNode|null,
|
||||
cloneToTViewData(blueprint).fill(null, bindingStartIndex), // data: TData,
|
||||
bindingStartIndex, // bindingStartIndex: number,
|
||||
initialViewLength, // viewQueryStartIndex: number,
|
||||
initialViewLength, // expandoStartIndex: number,
|
||||
null, // expandoInstructions: ExpandoInstructions|null,
|
||||
true, // firstTemplatePass: boolean,
|
||||
|
@ -619,11 +612,11 @@ export function createTView(
|
|||
id: viewIndex,
|
||||
blueprint: blueprint,
|
||||
template: templateFn,
|
||||
queries: null,
|
||||
viewQuery: viewQuery,
|
||||
node: null !,
|
||||
data: blueprint.slice().fill(null, bindingStartIndex),
|
||||
bindingStartIndex: bindingStartIndex,
|
||||
viewQueryStartIndex: initialViewLength,
|
||||
expandoStartIndex: initialViewLength,
|
||||
expandoInstructions: null,
|
||||
firstTemplatePass: true,
|
||||
|
@ -1031,13 +1024,18 @@ export function instantiateRootComponent<T>(
|
|||
/**
|
||||
* Resolve the matched directives on a node.
|
||||
*/
|
||||
function resolveDirectives(
|
||||
tView: TView, viewData: LView, directives: DirectiveDef<any>[] | null, tNode: TNode,
|
||||
export function resolveDirectives(
|
||||
tView: TView, lView: LView, tNode: TElementNode | TContainerNode | TElementContainerNode,
|
||||
localRefs: string[] | null): void {
|
||||
// Please make sure to have explicit type for `exportsMap`. Inferred type triggers bug in
|
||||
// tsickle.
|
||||
ngDevMode && assertEqual(tView.firstTemplatePass, true, 'should run on first template pass only');
|
||||
|
||||
if (!getBindingsEnabled()) return;
|
||||
|
||||
const directives: DirectiveDef<any>[]|null = findDirectiveMatches(tView, lView, tNode);
|
||||
const exportsMap: ({[key: string]: number} | null) = localRefs ? {'': -1} : null;
|
||||
|
||||
if (directives) {
|
||||
initNodeFlags(tNode, tView.data.length, directives.length);
|
||||
// When the same token is provided by several directives on the same node, some rules apply in
|
||||
|
@ -1059,7 +1057,7 @@ function resolveDirectives(
|
|||
const def = directives[i] as DirectiveDef<any>;
|
||||
|
||||
const directiveDefIdx = tView.data.length;
|
||||
baseResolveDirective(tView, viewData, def, def.factory);
|
||||
baseResolveDirective(tView, lView, def, def.factory);
|
||||
|
||||
saveNameToExportMap(tView.data !.length - 1, def, exportsMap);
|
||||
|
||||
|
@ -1766,8 +1764,8 @@ export function checkView<T>(hostView: LView, component: T) {
|
|||
|
||||
function executeViewQueryFn<T>(flags: RenderFlags, tView: TView, component: T): void {
|
||||
const viewQuery = tView.viewQuery;
|
||||
if (viewQuery) {
|
||||
setCurrentQueryIndex(tView.viewQueryStartIndex);
|
||||
if (viewQuery !== null) {
|
||||
setCurrentQueryIndex(0);
|
||||
viewQuery(flags, component);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,9 +9,9 @@
|
|||
import {ViewRef} from '../../linker/view_ref';
|
||||
|
||||
import {TNode} from './node';
|
||||
import {LQueries} from './query';
|
||||
import {RComment, RElement} from './renderer';
|
||||
import {HOST, LView, NEXT, PARENT, QUERIES, T_HOST} from './view';
|
||||
|
||||
import {HOST, LView, NEXT, PARENT, T_HOST} from './view';
|
||||
|
||||
|
||||
/**
|
||||
|
@ -26,8 +26,15 @@ export const TYPE = 1;
|
|||
* Uglify will inline these when minifying so there shouldn't be a cost.
|
||||
*/
|
||||
export const ACTIVE_INDEX = 2;
|
||||
// PARENT, NEXT, QUERIES and T_HOST are indices 3, 4, 5 and 6.
|
||||
|
||||
// PARENT and NEXT are indices 3 and 4
|
||||
// As we already have these constants in LView, we don't need to re-create them.
|
||||
|
||||
export const MOVED_VIEWS = 5;
|
||||
|
||||
// T_HOST is index 6
|
||||
// We already have this constants in LView, we don't need to re-create it.
|
||||
|
||||
export const NATIVE = 7;
|
||||
export const VIEW_REFS = 8;
|
||||
|
||||
|
@ -84,11 +91,11 @@ export interface LContainer extends Array<any> {
|
|||
[NEXT]: LView|LContainer|null;
|
||||
|
||||
/**
|
||||
* Queries active for this container - all the views inserted to / removed from
|
||||
* this container are reported to queries referenced here.
|
||||
* A collection of views created based on the underlying `<ng-template>` element but inserted into
|
||||
* a different `LContainer`. We need to track views created from a given declaration point since
|
||||
* queries collect matches from the embedded view declaration point and _not_ the insertion point.
|
||||
*/
|
||||
[QUERIES]: LQueries|null; // TODO(misko): This is abuse of `LContainer` since we are storing
|
||||
// `[QUERIES]` in it which are not needed for `LContainer` (only needed for Template)
|
||||
[MOVED_VIEWS]: LView[]|null;
|
||||
|
||||
/**
|
||||
* Pointer to the `TNode` which represents the host of the container.
|
||||
|
|
|
@ -9,99 +9,211 @@
|
|||
import {Type} from '../../interface/type';
|
||||
import {QueryList} from '../../linker';
|
||||
|
||||
import {TContainerNode, TElementContainerNode, TElementNode, TNode} from './node';
|
||||
import {TNode} from './node';
|
||||
import {TView} from './view';
|
||||
|
||||
/**
|
||||
* An object representing query metadata extracted from query annotations.
|
||||
*/
|
||||
export interface TQueryMetadata {
|
||||
predicate: Type<any>|string[];
|
||||
descendants: boolean;
|
||||
read: any;
|
||||
isStatic: boolean;
|
||||
}
|
||||
|
||||
/** Used for tracking queries (e.g. ViewChild, ContentChild). */
|
||||
/**
|
||||
* TQuery objects represent all the query-related data that remain the same from one view instance
|
||||
* to another and can be determined on the very first template pass. Most notably TQuery holds all
|
||||
* the matches for a given view.
|
||||
*/
|
||||
export interface TQuery {
|
||||
/**
|
||||
* Query metadata extracted from query annotations.
|
||||
*/
|
||||
metadata: TQueryMetadata;
|
||||
|
||||
/**
|
||||
* Index of a query in a declaration view in case of queries propagated to en embedded view, -1
|
||||
* for queries declared in a given view. We are storing this index so we can find a parent query
|
||||
* to clone for an embedded view (when an embedded view is created).
|
||||
*/
|
||||
indexInDeclarationView: number;
|
||||
|
||||
/**
|
||||
* Matches collected on the first template pass. Each match is a pair of:
|
||||
* - TNode index;
|
||||
* - match index;
|
||||
*
|
||||
* A TNode index can be either:
|
||||
* - a positive number (the most common case) to indicate a matching TNode;
|
||||
* - a negative number to indicate that a given query is crossing a <ng-template> element and
|
||||
* results from views created based on TemplateRef should be inserted at this place.
|
||||
*
|
||||
* A match index is a number used to find an actual value (for a given node) when query results
|
||||
* are materialized. This index can have one of the following values:
|
||||
* - -2 - indicates that we need to read a special token (TemplateRef, ViewContainerRef etc.);
|
||||
* - -1 - indicates that we need to read a default value based on the node type (TemplateRef for
|
||||
* ng-template and ElementRef for other elements);
|
||||
* - a positive number - index of an injectable to be read from the element injector.
|
||||
*/
|
||||
matches: number[]|null;
|
||||
|
||||
/**
|
||||
* A flag indicating if a given query crosses an <ng-template> element. This flag exists for
|
||||
* performance reasons: we can notice that queries not crossing any <ng-template> elements will
|
||||
* have matches from a given view only (and adapt processing accordingly).
|
||||
*/
|
||||
crossesNgTemplate: boolean;
|
||||
|
||||
/**
|
||||
* A method call when a given query is crossing an element (or element container). This is where a
|
||||
* given TNode is matched against a query predicate.
|
||||
* @param tView
|
||||
* @param tNode
|
||||
*/
|
||||
elementStart(tView: TView, tNode: TNode): void;
|
||||
|
||||
/**
|
||||
* A method called when processing the elementEnd instruction - this is mostly useful to determine
|
||||
* if a given content query should match any nodes past this point.
|
||||
* @param tNode
|
||||
*/
|
||||
elementEnd(tNode: TNode): void;
|
||||
|
||||
/**
|
||||
* A method called when processing the template instruction. This is where a
|
||||
* given TContainerNode is matched against a query predicate.
|
||||
* @param tView
|
||||
* @param tNode
|
||||
*/
|
||||
template(tView: TView, tNode: TNode): void;
|
||||
|
||||
/**
|
||||
* A query-related method called when an embedded TView is created based on the content of a
|
||||
* <ng-template> element. We call this method to determine if a given query should be propagated
|
||||
* to the embedded view and if so - return a cloned TQuery for this embedded view.
|
||||
* @param tNode
|
||||
* @param childQueryIndex
|
||||
*/
|
||||
embeddedTView(tNode: TNode, childQueryIndex: number): TQuery|null;
|
||||
}
|
||||
|
||||
/**
|
||||
* TQueries represent a collection of individual TQuery objects tracked in a given view. Most of the
|
||||
* methods on this interface are simple proxy methods to the corresponding functionality on TQuery.
|
||||
*/
|
||||
export interface TQueries {
|
||||
/**
|
||||
* Adds a new TQuery to a collection of queries tracked in a given view.
|
||||
* @param tQuery
|
||||
*/
|
||||
track(tQuery: TQuery): void;
|
||||
|
||||
/**
|
||||
* Returns a TQuery instance for at the given index in the queries array.
|
||||
* @param index
|
||||
*/
|
||||
getByIndex(index: number): TQuery;
|
||||
|
||||
/**
|
||||
* Returns the number of queries tracked in a given view.
|
||||
*/
|
||||
length: number;
|
||||
|
||||
/**
|
||||
* A proxy method that iterates over all the TQueries in a given TView and calls the corresponding
|
||||
* `elementStart` on each and every TQuery.
|
||||
* @param tView
|
||||
* @param tNode
|
||||
*/
|
||||
elementStart(tView: TView, tNode: TNode): void;
|
||||
|
||||
/**
|
||||
* A proxy method that iterates over all the TQueries in a given TView and calls the corresponding
|
||||
* `elementEnd` on each and every TQuery.
|
||||
* @param tNode
|
||||
*/
|
||||
elementEnd(tNode: TNode): void;
|
||||
|
||||
/**
|
||||
* A proxy method that iterates over all the TQueries in a given TView and calls the corresponding
|
||||
* `template` on each and every TQuery.
|
||||
* @param tView
|
||||
* @param tNode
|
||||
*/
|
||||
template(tView: TView, tNode: TNode): void;
|
||||
|
||||
/**
|
||||
* A proxy method that iterates over all the TQueries in a given TView and calls the corresponding
|
||||
* `embeddedTView` on each and every TQuery.
|
||||
* @param tNode
|
||||
*/
|
||||
embeddedTView(tNode: TNode): TQueries|null;
|
||||
}
|
||||
|
||||
/**
|
||||
* An interface that represents query-related information specific to a view instance. Most notably
|
||||
* it contains:
|
||||
* - materialized query matches;
|
||||
* - a pointer to a QueryList where materialized query results should be reported.
|
||||
*/
|
||||
export interface LQuery<T> {
|
||||
/**
|
||||
* Materialized query matches for a given view only (!). Results are initialized lazily so the
|
||||
* array of matches is set to `null` initially.
|
||||
*/
|
||||
matches: (T|null)[]|null;
|
||||
|
||||
/**
|
||||
* A QueryList where materialized query results should be reported.
|
||||
*/
|
||||
queryList: QueryList<T>;
|
||||
|
||||
/**
|
||||
* Clones an LQuery for an embedded view. A cloned query shares the same `QueryList` but has a
|
||||
* separate collection of materialized matches.
|
||||
*/
|
||||
clone(): LQuery<T>;
|
||||
|
||||
/**
|
||||
* Called when an embedded view, impacting results of this query, is inserted or removed.
|
||||
*/
|
||||
setDirty(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* lQueries represent a collection of individual LQuery objects tracked in a given view.
|
||||
*/
|
||||
export interface LQueries {
|
||||
/**
|
||||
* The parent LQueries instance.
|
||||
*
|
||||
* When there is a content query, a new LQueries instance is created to avoid mutating any
|
||||
* existing LQueries. After we are done searching content children, the parent property allows
|
||||
* us to traverse back up to the original LQueries instance to continue to search for matches
|
||||
* in the main view.
|
||||
* A collection of queries tracked in a given view.
|
||||
*/
|
||||
parent: LQueries|null;
|
||||
queries: LQuery<any>[];
|
||||
|
||||
/**
|
||||
* The index of the node on which this LQueries instance was created / cloned in a given LView.
|
||||
*
|
||||
* This index is stored to minimize LQueries cloning: we can observe that LQueries can be mutated
|
||||
* only under 2 conditions:
|
||||
* - we are crossing an element that has directives with content queries (new queries are added);
|
||||
* - we are descending into element hierarchy (creating a child element of an existing element)
|
||||
* and the current LQueries object is tracking shallow queries (shallow queries are removed).
|
||||
*
|
||||
* Since LQueries are not cloned systematically we need to know exactly where (on each element)
|
||||
* cloning occurred, so we can properly restore the set of tracked queries when going up the
|
||||
* elements hierarchy.
|
||||
*
|
||||
* Always set to -1 for view queries as view queries are created before we process any node in a
|
||||
* given view.
|
||||
* A method called when a new embedded view is created. As a result a set of LQueries applicable
|
||||
* for a new embedded view is instantiated (cloned) from the declaration view.
|
||||
* @param tView
|
||||
*/
|
||||
nodeIndex: number;
|
||||
createEmbeddedView(tView: TView): LQueries|null;
|
||||
|
||||
/**
|
||||
* Ask queries to prepare a copy of itself. This ensures that:
|
||||
* - tracking new queries on content nodes doesn't mutate list of queries tracked on a parent
|
||||
* node;
|
||||
* - we don't track shallow queries when descending into elements hierarchy.
|
||||
*
|
||||
* We will clone LQueries before constructing content queries
|
||||
* A method called when an embedded view is inserted into a container. As a result all impacted
|
||||
* `LQuery` objects (and associated `QueryList`) are marked as dirty.
|
||||
* @param tView
|
||||
*/
|
||||
clone(tNode: TNode): LQueries;
|
||||
insertView(tView: TView): void;
|
||||
|
||||
/**
|
||||
* Notify `LQueries` that a new `TNode` has been created and needs to be added to query results
|
||||
* if matching query predicate.
|
||||
* A method called when an embedded view is detached from a container. As a result all impacted
|
||||
* `LQuery` objects (and associated `QueryList`) are marked as dirty.
|
||||
* @param tView
|
||||
*/
|
||||
addNode(tNode: TElementNode|TContainerNode|TElementContainerNode): void;
|
||||
|
||||
/**
|
||||
* Notify `LQueries` that a new `TNode` has been created and needs to be added to query results
|
||||
* if matching query predicate. This is a special mode invoked if the query container has to
|
||||
* be created out of order (e.g. view created in the constructor of a directive).
|
||||
*/
|
||||
insertNodeBeforeViews(tNode: TElementNode|TContainerNode|TElementContainerNode): void;
|
||||
|
||||
/**
|
||||
* Notify `LQueries` that a new LContainer was added to ivy data structures. As a result we need
|
||||
* to prepare room for views that might be inserted into this container.
|
||||
*/
|
||||
container(): LQueries|null;
|
||||
|
||||
/**
|
||||
* Notify `LQueries` that a new `LView` has been created. As a result we need to prepare room
|
||||
* and collect nodes that match query predicate.
|
||||
*/
|
||||
createView(): LQueries|null;
|
||||
|
||||
/**
|
||||
* Notify `LQueries` that a new `LView` has been added to `LContainer`. As a result all
|
||||
* the matching nodes from this view should be added to container's queries.
|
||||
*/
|
||||
insertView(newViewIndex: number): void;
|
||||
|
||||
/**
|
||||
* Notify `LQueries` that an `LView` has been removed from `LContainer`. As a result all
|
||||
* the matching nodes from this view should be removed from container's queries.
|
||||
*/
|
||||
removeView(): void;
|
||||
|
||||
/**
|
||||
* Add additional `QueryList` to track.
|
||||
*
|
||||
* @param queryList `QueryList` to update with changes.
|
||||
* @param predicate Either `Type` or selector array of [key, value] predicates.
|
||||
* @param descend If true the query will recursively apply to the children.
|
||||
* @param read Indicates which token should be read from DI for this query.
|
||||
*/
|
||||
track<T>(
|
||||
queryList: QueryList<T>, predicate: Type<any>|string[], descend?: boolean,
|
||||
read?: Type<T>): void;
|
||||
detachView(tView: TView): void;
|
||||
}
|
||||
|
||||
|
||||
// Note: This hack is necessary so we don't erroneously get a circular dependency
|
||||
// failure based on types.
|
||||
export const unusedValueExportToPlacateAjd = 1;
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
import {InjectionToken} from '../../di/injection_token';
|
||||
import {Injector} from '../../di/injector';
|
||||
import {Type} from '../../interface/type';
|
||||
import {QueryList} from '../../linker';
|
||||
import {SchemaMetadata} from '../../metadata';
|
||||
import {Sanitizer} from '../../sanitization/security';
|
||||
|
||||
|
@ -18,7 +17,7 @@ import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefList, HostBin
|
|||
import {I18nUpdateOpCodes, TI18n} from './i18n';
|
||||
import {TElementNode, TNode, TViewNode} from './node';
|
||||
import {PlayerHandler} from './player';
|
||||
import {LQueries} from './query';
|
||||
import {LQueries, TQueries} from './query';
|
||||
import {RElement, Renderer3, RendererFactory3} from './renderer';
|
||||
|
||||
|
||||
|
@ -42,11 +41,11 @@ export const RENDERER = 12;
|
|||
export const SANITIZER = 13;
|
||||
export const CHILD_HEAD = 14;
|
||||
export const CHILD_TAIL = 15;
|
||||
export const CONTENT_QUERIES = 16;
|
||||
export const DECLARATION_VIEW = 17;
|
||||
export const DECLARATION_VIEW = 16;
|
||||
export const DECLARATION_LCONTAINER = 17;
|
||||
export const PREORDER_HOOK_FLAGS = 18;
|
||||
/** Size of LView's header. Necessary to adjust for it when setting slots. */
|
||||
export const HEADER_OFFSET = 20;
|
||||
export const HEADER_OFFSET = 19;
|
||||
|
||||
|
||||
// This interface replaces the real LView interface if it is an arg or a
|
||||
|
@ -179,13 +178,6 @@ export interface LView extends Array<any> {
|
|||
*/
|
||||
[CHILD_TAIL]: LView|LContainer|null;
|
||||
|
||||
/**
|
||||
* Stores QueryLists associated with content queries of a directive. This data structure is
|
||||
* filled-in as part of a directive creation process and is later used to retrieve a QueryList to
|
||||
* be refreshed.
|
||||
*/
|
||||
[CONTENT_QUERIES]: QueryList<any>[]|null;
|
||||
|
||||
/**
|
||||
* View where this view's template was declared.
|
||||
*
|
||||
|
@ -212,6 +204,16 @@ export interface LView extends Array<any> {
|
|||
*/
|
||||
[DECLARATION_VIEW]: LView|null;
|
||||
|
||||
/**
|
||||
* A declaration point of embedded views (ones instantiated based on the content of a
|
||||
* <ng-template>), null for other types of views.
|
||||
*
|
||||
* We need to track all embedded views created from a given declaration point so we can prepare
|
||||
* query matches in a proper order (query matches are ordered based on their declaration point and
|
||||
* _not_ the insertion point).
|
||||
*/
|
||||
[DECLARATION_LCONTAINER]: LContainer|null;
|
||||
|
||||
/**
|
||||
* More flags for this view. See PreOrderHookFlags for more info.
|
||||
*/
|
||||
|
@ -410,17 +412,6 @@ export interface TView {
|
|||
*/
|
||||
staticContentQueries: boolean;
|
||||
|
||||
/**
|
||||
* The index where the viewQueries section of `LView` begins. This section contains
|
||||
* view queries defined for a component/directive.
|
||||
*
|
||||
* We store this start index so we know where the list of view queries starts.
|
||||
* This is required when we invoke view queries at runtime. We invoke queries one by one and
|
||||
* increment query index after each iteration. This information helps us to reset index back to
|
||||
* the beginning of view query list before we invoke view queries again.
|
||||
*/
|
||||
viewQueryStartIndex: number;
|
||||
|
||||
/**
|
||||
* A reference to the first child node located in the view.
|
||||
*/
|
||||
|
@ -550,7 +541,19 @@ export interface TView {
|
|||
components: number[]|null;
|
||||
|
||||
/**
|
||||
* A list of indices for child directives that have content queries.
|
||||
* A collection of queries tracked in a given view.
|
||||
*/
|
||||
queries: TQueries|null;
|
||||
|
||||
/**
|
||||
* An array of indices pointing to directives with content queries alongside with the
|
||||
* corresponding
|
||||
* query index. Each entry in this array is a tuple of:
|
||||
* - index of the first content query index declared by a given directive;
|
||||
* - index of a directive.
|
||||
*
|
||||
* We are storing those indexes so we can refresh content queries as part of a view refresh
|
||||
* process.
|
||||
*/
|
||||
contentQueries: number[]|null;
|
||||
|
||||
|
|
|
@ -11,18 +11,18 @@ import {assertDefined, assertDomNode} from '../util/assert';
|
|||
|
||||
import {assertLContainer, assertLView} from './assert';
|
||||
import {attachPatchData} from './context_discovery';
|
||||
import {CONTAINER_HEADER_OFFSET, LContainer, NATIVE, unusedValueExportToPlacateAjd as unused1} from './interfaces/container';
|
||||
import {CONTAINER_HEADER_OFFSET, LContainer, MOVED_VIEWS, NATIVE, unusedValueExportToPlacateAjd as unused1} from './interfaces/container';
|
||||
import {ComponentDef} from './interfaces/definition';
|
||||
import {NodeInjectorFactory} from './interfaces/injector';
|
||||
import {TElementContainerNode, TElementNode, TIcuContainerNode, TNode, TNodeFlags, TNodeType, TProjectionNode, TViewNode, unusedValueExportToPlacateAjd as unused2} from './interfaces/node';
|
||||
import {unusedValueExportToPlacateAjd as unused3} from './interfaces/projection';
|
||||
import {ProceduralRenderer3, RElement, RNode, RText, Renderer3, isProceduralRenderer, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer';
|
||||
import {isLContainer, isLView, isRootView} from './interfaces/type_checks';
|
||||
import {CHILD_HEAD, CLEANUP, FLAGS, HOST, HookData, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, TVIEW, T_HOST, unusedValueExportToPlacateAjd as unused5} from './interfaces/view';
|
||||
import {CHILD_HEAD, CLEANUP, DECLARATION_LCONTAINER, FLAGS, HOST, HookData, LView, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, TVIEW, T_HOST, unusedValueExportToPlacateAjd as unused5} from './interfaces/view';
|
||||
import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert';
|
||||
import {renderStringify} from './util/misc_utils';
|
||||
import {findComponentView, getLViewParent} from './util/view_traversal_utils';
|
||||
import {getNativeByTNode, getNativeByTNodeOrNull, unwrapRNode, viewAttachedToContainer} from './util/view_utils';
|
||||
import {getNativeByTNode, getNativeByTNodeOrNull, unwrapRNode} from './util/view_utils';
|
||||
|
||||
const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4 + unused5;
|
||||
|
||||
|
@ -217,15 +217,46 @@ export function insertView(lView: LView, lContainer: LContainer, index: number)
|
|||
|
||||
lView[PARENT] = lContainer;
|
||||
|
||||
// Notify query that a new view has been added
|
||||
if (lView[QUERIES]) {
|
||||
lView[QUERIES] !.insertView(index);
|
||||
// track views where declaration and insertion points are different
|
||||
const declarationLContainer = lView[DECLARATION_LCONTAINER];
|
||||
if (declarationLContainer !== null && lContainer !== declarationLContainer) {
|
||||
trackMovedView(declarationLContainer, lView);
|
||||
}
|
||||
|
||||
// notify query that a new view has been added
|
||||
const lQueries = lView[QUERIES];
|
||||
if (lQueries !== null) {
|
||||
lQueries.insertView(lView[TVIEW]);
|
||||
}
|
||||
|
||||
// Sets the attached flag
|
||||
lView[FLAGS] |= LViewFlags.Attached;
|
||||
}
|
||||
|
||||
/**
|
||||
* Track views created from the declaration container (TemplateRef) and inserted into a
|
||||
* different LContainer.
|
||||
*/
|
||||
function trackMovedView(declarationContainer: LContainer, lView: LView) {
|
||||
ngDevMode && assertLContainer(declarationContainer);
|
||||
const declaredViews = declarationContainer[MOVED_VIEWS];
|
||||
if (declaredViews === null) {
|
||||
declarationContainer[MOVED_VIEWS] = [lView];
|
||||
} else {
|
||||
declaredViews.push(lView);
|
||||
}
|
||||
}
|
||||
|
||||
function detachMovedView(declarationContainer: LContainer, lView: LView) {
|
||||
ngDevMode && assertLContainer(declarationContainer);
|
||||
ngDevMode && assertDefined(
|
||||
declarationContainer[MOVED_VIEWS],
|
||||
'A projected view should belong to a non-empty projected views collection');
|
||||
const projectedViews = declarationContainer[MOVED_VIEWS] !;
|
||||
const declaredViewIndex = projectedViews.indexOf(lView);
|
||||
projectedViews.splice(declaredViewIndex, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detaches a view from a container.
|
||||
*
|
||||
|
@ -241,17 +272,26 @@ export function detachView(lContainer: LContainer, removeIndex: number): LView|u
|
|||
|
||||
const indexInContainer = CONTAINER_HEADER_OFFSET + removeIndex;
|
||||
const viewToDetach = lContainer[indexInContainer];
|
||||
|
||||
if (viewToDetach) {
|
||||
const declarationLContainer = viewToDetach[DECLARATION_LCONTAINER];
|
||||
if (declarationLContainer !== null && declarationLContainer !== lContainer) {
|
||||
detachMovedView(declarationLContainer, viewToDetach);
|
||||
}
|
||||
|
||||
|
||||
if (removeIndex > 0) {
|
||||
lContainer[indexInContainer - 1][NEXT] = viewToDetach[NEXT] as LView;
|
||||
}
|
||||
lContainer.splice(CONTAINER_HEADER_OFFSET + removeIndex, 1);
|
||||
const removedLView = lContainer.splice(CONTAINER_HEADER_OFFSET + removeIndex, 1)[0];
|
||||
addRemoveViewFromContainer(viewToDetach, false);
|
||||
|
||||
if ((viewToDetach[FLAGS] & LViewFlags.Attached) &&
|
||||
!(viewToDetach[FLAGS] & LViewFlags.Destroyed) && viewToDetach[QUERIES]) {
|
||||
viewToDetach[QUERIES] !.removeView();
|
||||
// notify query that a view has been removed
|
||||
const lQueries = removedLView[QUERIES];
|
||||
if (lQueries !== null) {
|
||||
lQueries.detachView(removedLView[TVIEW]);
|
||||
}
|
||||
|
||||
viewToDetach[PARENT] = null;
|
||||
viewToDetach[NEXT] = null;
|
||||
// Unsets the attached flag
|
||||
|
@ -342,9 +382,20 @@ function cleanUpView(view: LView | LContainer): void {
|
|||
ngDevMode && ngDevMode.rendererDestroy++;
|
||||
(view[RENDERER] as ProceduralRenderer3).destroy();
|
||||
}
|
||||
// For embedded views still attached to a container: remove query result from this view.
|
||||
if (viewAttachedToContainer(view) && view[QUERIES]) {
|
||||
view[QUERIES] !.removeView();
|
||||
|
||||
const declarationContainer = view[DECLARATION_LCONTAINER];
|
||||
// we are dealing with an embedded view that is still inserted into a container
|
||||
if (declarationContainer !== null && isLContainer(view[PARENT])) {
|
||||
// and this is a projected view
|
||||
if (declarationContainer !== view[PARENT]) {
|
||||
detachMovedView(declarationContainer, view);
|
||||
}
|
||||
|
||||
// For embedded views still attached to a container: remove query result from this view.
|
||||
const lQueries = view[QUERIES];
|
||||
if (lQueries !== null) {
|
||||
lQueries.detachView(view[TVIEW]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -878,16 +929,18 @@ function executeActionOnNode(
|
|||
renderer: Renderer3, action: WalkTNodeTreeAction, lView: LView, tNode: TNode,
|
||||
renderParent: RElement | null, beforeNode: RNode | null | undefined): void {
|
||||
const nodeType = tNode.type;
|
||||
if (nodeType === TNodeType.ElementContainer || nodeType === TNodeType.IcuContainer) {
|
||||
executeActionOnElementContainerOrIcuContainer(
|
||||
renderer, action, lView, tNode as TElementContainerNode | TIcuContainerNode, renderParent,
|
||||
beforeNode);
|
||||
} else if (nodeType === TNodeType.Projection) {
|
||||
executeActionOnProjection(
|
||||
renderer, action, lView, tNode as TProjectionNode, renderParent, beforeNode);
|
||||
} else {
|
||||
ngDevMode && assertNodeOfPossibleTypes(tNode, TNodeType.Element, TNodeType.Container);
|
||||
executeActionOnElementOrContainer(
|
||||
action, renderer, renderParent, lView[tNode.index], beforeNode);
|
||||
if (!(tNode.flags & TNodeFlags.isDetached)) {
|
||||
if (nodeType === TNodeType.ElementContainer || nodeType === TNodeType.IcuContainer) {
|
||||
executeActionOnElementContainerOrIcuContainer(
|
||||
renderer, action, lView, tNode as TElementContainerNode | TIcuContainerNode, renderParent,
|
||||
beforeNode);
|
||||
} else if (nodeType === TNodeType.Projection) {
|
||||
executeActionOnProjection(
|
||||
renderer, action, lView, tNode as TProjectionNode, renderParent, beforeNode);
|
||||
} else {
|
||||
ngDevMode && assertNodeOfPossibleTypes(tNode, TNodeType.Element, TNodeType.Container);
|
||||
executeActionOnElementOrContainer(
|
||||
action, renderer, renderParent, lView[tNode.index], beforeNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,195 +13,240 @@ import {Type} from '../interface/type';
|
|||
import {ElementRef as ViewEngine_ElementRef} from '../linker/element_ref';
|
||||
import {QueryList} from '../linker/query_list';
|
||||
import {TemplateRef as ViewEngine_TemplateRef} from '../linker/template_ref';
|
||||
import {assertDataInRange, assertDefined, assertEqual} from '../util/assert';
|
||||
import {ViewContainerRef} from '../linker/view_container_ref';
|
||||
import {assertDataInRange, assertDefined, throwError} from '../util/assert';
|
||||
import {stringify} from '../util/stringify';
|
||||
|
||||
import {assertPreviousIsParent} from './assert';
|
||||
import {assertFirstTemplatePass, assertLContainer} from './assert';
|
||||
import {getNodeInjectable, locateDirectiveOrProvider} from './di';
|
||||
import {NG_ELEMENT_ID} from './fields';
|
||||
import {store} from './instructions/all';
|
||||
import {storeCleanupWithContext} from './instructions/shared';
|
||||
import {CONTAINER_HEADER_OFFSET, LContainer, MOVED_VIEWS} from './interfaces/container';
|
||||
import {unusedValueExportToPlacateAjd as unused1} from './interfaces/definition';
|
||||
import {unusedValueExportToPlacateAjd as unused2} from './interfaces/injector';
|
||||
import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeType, unusedValueExportToPlacateAjd as unused3} from './interfaces/node';
|
||||
import {LQueries, unusedValueExportToPlacateAjd as unused4} from './interfaces/query';
|
||||
import {isContentQueryHost} from './interfaces/type_checks';
|
||||
import {CONTENT_QUERIES, HEADER_OFFSET, LView, QUERIES, TVIEW, TView} from './interfaces/view';
|
||||
import {getCurrentQueryIndex, getIsParent, getLView, getPreviousOrParentTNode, isCreationMode, setCurrentQueryIndex} from './state';
|
||||
import {loadInternal} from './util/view_utils';
|
||||
import {createElementRef, createTemplateRef} from './view_engine_compatibility';
|
||||
import {LQueries, LQuery, TQueries, TQuery, TQueryMetadata, unusedValueExportToPlacateAjd as unused4} from './interfaces/query';
|
||||
import {DECLARATION_LCONTAINER, LView, PARENT, QUERIES, TVIEW, TView} from './interfaces/view';
|
||||
import {assertNodeOfPossibleTypes} from './node_assert';
|
||||
import {getCurrentQueryIndex, getLView, getPreviousOrParentTNode, isCreationMode, setCurrentQueryIndex} from './state';
|
||||
import {createContainerRef, createElementRef, createTemplateRef} from './view_engine_compatibility';
|
||||
|
||||
const unusedValueToPlacateAjd = unused1 + unused2 + unused3 + unused4;
|
||||
|
||||
/**
|
||||
* A predicate which determines if a given element/directive should be included in the query
|
||||
* results.
|
||||
*/
|
||||
export interface QueryPredicate<T> {
|
||||
/**
|
||||
* If looking for directives then it contains the directive type.
|
||||
*/
|
||||
type: Type<T>|null;
|
||||
|
||||
/**
|
||||
* If selector then contains local names to query for.
|
||||
*/
|
||||
selector: string[]|null;
|
||||
|
||||
/**
|
||||
* Indicates which token should be read from DI for this query.
|
||||
*/
|
||||
read: Type<T>|null;
|
||||
class LQuery_<T> implements LQuery<T> {
|
||||
matches: (T|null)[]|null = null;
|
||||
constructor(public queryList: QueryList<T>) {}
|
||||
clone(): LQuery<T> { return new LQuery_(this.queryList); }
|
||||
setDirty(): void { this.queryList.setDirty(); }
|
||||
}
|
||||
|
||||
/**
|
||||
* An object representing a query, which is a combination of:
|
||||
* - query predicate to determines if a given element/directive should be included in the query
|
||||
* - values collected based on a predicate
|
||||
* - `QueryList` to which collected values should be reported
|
||||
*/
|
||||
class LQuery<T> {
|
||||
constructor(
|
||||
/**
|
||||
* Next query. Used when queries are stored as a linked list in `LQueries`.
|
||||
*/
|
||||
public next: LQuery<any>|null,
|
||||
class LQueries_ implements LQueries {
|
||||
constructor(public queries: LQuery<any>[] = []) {}
|
||||
|
||||
/**
|
||||
* Destination to which the value should be added.
|
||||
*/
|
||||
public list: QueryList<T>,
|
||||
createEmbeddedView(tView: TView): LQueries|null {
|
||||
const tQueries = tView.queries;
|
||||
if (tQueries !== null) {
|
||||
const noOfInheritedQueries =
|
||||
tView.contentQueries !== null ? tView.contentQueries[0] : tQueries.length;
|
||||
const viewLQueries: LQuery<any>[] = new Array(noOfInheritedQueries);
|
||||
|
||||
/**
|
||||
* A predicate which determines if a given element/directive should be included in the query
|
||||
* results.
|
||||
*/
|
||||
public predicate: QueryPredicate<T>,
|
||||
// An embedded view has queries propagated from a declaration view at the beginning of the
|
||||
// TQueries collection and up until a first content query declared in the embedded view. Only
|
||||
// propagated LQueries are created at this point (LQuery corresponding to declared content
|
||||
// queries will be instantiated from the content query instructions for each directive).
|
||||
for (let i = 0; i < noOfInheritedQueries; i++) {
|
||||
const tQuery = tQueries.getByIndex(i);
|
||||
const parentLQuery = this.queries[tQuery.indexInDeclarationView];
|
||||
viewLQueries[i] = parentLQuery.clone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Values which have been located.
|
||||
* This is what builds up the `QueryList._valuesTree`.
|
||||
*/
|
||||
public values: any[],
|
||||
return new LQueries_(viewLQueries);
|
||||
}
|
||||
|
||||
/**
|
||||
* A pointer to an array that stores collected values from views. This is necessary so we
|
||||
* know a container into which to insert nodes collected from views.
|
||||
*/
|
||||
public containerValues: any[]|null) {}
|
||||
return null;
|
||||
}
|
||||
|
||||
insertView(tView: TView): void { this.dirtyQueriesWithMatches(tView); }
|
||||
|
||||
detachView(tView: TView): void { this.dirtyQueriesWithMatches(tView); }
|
||||
|
||||
private dirtyQueriesWithMatches(tView: TView) {
|
||||
for (let i = 0; i < this.queries.length; i++) {
|
||||
if (getTQuery(tView, i).matches !== null) {
|
||||
this.queries[i].setDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class LQueries_ implements LQueries {
|
||||
class TQueryMetadata_ implements TQueryMetadata {
|
||||
constructor(
|
||||
public parent: LQueries_|null, private shallow: LQuery<any>|null,
|
||||
private deep: LQuery<any>|null, public nodeIndex: number = -1) {}
|
||||
public predicate: Type<any>|string[], public descendants: boolean, public read: any,
|
||||
public isStatic: boolean) {}
|
||||
}
|
||||
|
||||
track<T>(queryList: QueryList<T>, predicate: Type<T>|string[], descend?: boolean, read?: Type<T>):
|
||||
void {
|
||||
if (descend) {
|
||||
this.deep = createLQuery(this.deep, queryList, predicate, read != null ? read : null);
|
||||
class TQueries_ implements TQueries {
|
||||
constructor(private queries: TQuery[] = []) {}
|
||||
|
||||
elementStart(tView: TView, tNode: TNode): void {
|
||||
ngDevMode && assertFirstTemplatePass(
|
||||
tView, 'Queries should collect results on the first template pass only');
|
||||
for (let query of this.queries) {
|
||||
query.elementStart(tView, tNode);
|
||||
}
|
||||
}
|
||||
elementEnd(tNode: TNode): void {
|
||||
for (let query of this.queries) {
|
||||
query.elementEnd(tNode);
|
||||
}
|
||||
}
|
||||
embeddedTView(tNode: TNode): TQueries|null {
|
||||
let queriesForTemplateRef: TQuery[]|null = null;
|
||||
|
||||
for (let i = 0; i < this.length; i++) {
|
||||
const childQueryIndex = queriesForTemplateRef !== null ? queriesForTemplateRef.length : 0;
|
||||
const tqueryClone = this.getByIndex(i).embeddedTView(tNode, childQueryIndex);
|
||||
|
||||
if (tqueryClone) {
|
||||
tqueryClone.indexInDeclarationView = i;
|
||||
if (queriesForTemplateRef !== null) {
|
||||
queriesForTemplateRef.push(tqueryClone);
|
||||
} else {
|
||||
queriesForTemplateRef = [tqueryClone];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return queriesForTemplateRef !== null ? new TQueries_(queriesForTemplateRef) : null;
|
||||
}
|
||||
|
||||
template(tView: TView, tNode: TNode): void {
|
||||
ngDevMode && assertFirstTemplatePass(
|
||||
tView, 'Queries should collect results on the first template pass only');
|
||||
for (let query of this.queries) {
|
||||
query.template(tView, tNode);
|
||||
}
|
||||
}
|
||||
|
||||
getByIndex(index: number): TQuery {
|
||||
ngDevMode && assertDataInRange(this.queries, index);
|
||||
return this.queries[index];
|
||||
}
|
||||
|
||||
get length(): number { return this.queries.length; }
|
||||
|
||||
track(tquery: TQuery): void { this.queries.push(tquery); }
|
||||
}
|
||||
|
||||
class TQuery_ implements TQuery {
|
||||
matches: number[]|null = null;
|
||||
indexInDeclarationView = -1;
|
||||
crossesNgTemplate = false;
|
||||
|
||||
/**
|
||||
* A node index on which a query was declared (-1 for view queries and ones inherited from the
|
||||
* declaration template). We use this index (alongside with _appliesToNextNode flag) to know
|
||||
* when to apply content queries to elements in a template.
|
||||
*/
|
||||
private _declarationNodeIndex: number;
|
||||
|
||||
/**
|
||||
* A flag indicating if a given query still applies to nodes it is crossing. We use this flag
|
||||
* (alongside with _declarationNodeIndex) to know when to stop applying content queries to
|
||||
* elements in a template.
|
||||
*/
|
||||
private _appliesToNextNode = true;
|
||||
|
||||
constructor(public metadata: TQueryMetadata, nodeIndex: number = -1) {
|
||||
this._declarationNodeIndex = nodeIndex;
|
||||
}
|
||||
|
||||
elementStart(tView: TView, tNode: TNode): void {
|
||||
if (this.isApplyingToNode(tNode)) {
|
||||
this.matchTNode(tView, tNode);
|
||||
}
|
||||
}
|
||||
|
||||
elementEnd(tNode: TNode): void {
|
||||
if (this._declarationNodeIndex === tNode.index) {
|
||||
this._appliesToNextNode = false;
|
||||
}
|
||||
}
|
||||
|
||||
template(tView: TView, tNode: TNode): void { this.elementStart(tView, tNode); }
|
||||
|
||||
embeddedTView(tNode: TNode, childQueryIndex: number): TQuery|null {
|
||||
if (this.isApplyingToNode(tNode)) {
|
||||
this.crossesNgTemplate = true;
|
||||
// A marker indicating a `<ng-template>` element (a placeholder for query results from
|
||||
// embedded views created based on this `<ng-template>`).
|
||||
this.addMatch(-tNode.index, childQueryIndex);
|
||||
return new TQuery_(this.metadata);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private isApplyingToNode(tNode: TNode): boolean {
|
||||
if (this._appliesToNextNode && this.metadata.descendants === false) {
|
||||
return this._declarationNodeIndex === (tNode.parent ? tNode.parent.index : -1);
|
||||
}
|
||||
return this._appliesToNextNode;
|
||||
}
|
||||
|
||||
private matchTNode(tView: TView, tNode: TNode): void {
|
||||
if (Array.isArray(this.metadata.predicate)) {
|
||||
this.matchTNodeByLocalNames(tView, tNode, this.metadata.predicate as string[]);
|
||||
} else {
|
||||
this.shallow = createLQuery(this.shallow, queryList, predicate, read != null ? read : null);
|
||||
const typePredicate = this.metadata.predicate as any;
|
||||
if (typePredicate === ViewEngine_TemplateRef) {
|
||||
this.matchTNodeByTemplateRef(tView, tNode);
|
||||
} else {
|
||||
this.matchTNodeByType(tView, tNode, typePredicate);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
clone(tNode: TNode): LQueries {
|
||||
return this.shallow !== null || isContentQueryHost(tNode) ?
|
||||
new LQueries_(this, null, this.deep, tNode.index) :
|
||||
this;
|
||||
}
|
||||
|
||||
container(): LQueries|null {
|
||||
const shallowResults = copyQueriesToContainer(this.shallow);
|
||||
const deepResults = copyQueriesToContainer(this.deep);
|
||||
return shallowResults || deepResults ? new LQueries_(this, shallowResults, deepResults) : null;
|
||||
}
|
||||
|
||||
createView(): LQueries|null {
|
||||
const shallowResults = copyQueriesToView(this.shallow);
|
||||
const deepResults = copyQueriesToView(this.deep);
|
||||
|
||||
return shallowResults || deepResults ? new LQueries_(this, shallowResults, deepResults) : null;
|
||||
}
|
||||
|
||||
insertView(index: number): void {
|
||||
insertView(index, this.shallow);
|
||||
insertView(index, this.deep);
|
||||
}
|
||||
|
||||
addNode(tNode: TElementNode|TContainerNode|TElementContainerNode): void {
|
||||
add(this.deep, tNode, false);
|
||||
add(this.shallow, tNode, false);
|
||||
}
|
||||
|
||||
insertNodeBeforeViews(tNode: TElementNode|TContainerNode|TElementContainerNode): void {
|
||||
add(this.deep, tNode, true);
|
||||
add(this.shallow, tNode, true);
|
||||
}
|
||||
|
||||
removeView(): void {
|
||||
removeView(this.shallow);
|
||||
removeView(this.deep);
|
||||
}
|
||||
}
|
||||
|
||||
function copyQueriesToContainer(query: LQuery<any>| null): LQuery<any>|null {
|
||||
let result: LQuery<any>|null = null;
|
||||
|
||||
while (query) {
|
||||
const containerValues: any[] = []; // prepare room for views
|
||||
query.values.push(containerValues);
|
||||
result = new LQuery<any>(result, query.list, query.predicate, containerValues, null);
|
||||
query = query.next;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function copyQueriesToView(query: LQuery<any>| null): LQuery<any>|null {
|
||||
let result: LQuery<any>|null = null;
|
||||
|
||||
while (query) {
|
||||
result = new LQuery<any>(result, query.list, query.predicate, [], query.values);
|
||||
query = query.next;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function insertView(index: number, query: LQuery<any>| null) {
|
||||
while (query) {
|
||||
ngDevMode && assertViewQueryhasPointerToDeclarationContainer(query);
|
||||
query.containerValues !.splice(index, 0, query.values);
|
||||
|
||||
// mark a query as dirty only when inserted view had matching modes
|
||||
if (query.values.length) {
|
||||
query.list.setDirty();
|
||||
private matchTNodeByLocalNames(tView: TView, tNode: TNode, localNames: string[]): void {
|
||||
for (let i = 0; i < localNames.length; i++) {
|
||||
this.matchTNodeWithReadOption(tView, tNode, getIdxOfMatchingSelector(tNode, localNames[i]));
|
||||
}
|
||||
|
||||
query = query.next;
|
||||
}
|
||||
}
|
||||
|
||||
function removeView(query: LQuery<any>| null) {
|
||||
while (query) {
|
||||
ngDevMode && assertViewQueryhasPointerToDeclarationContainer(query);
|
||||
private matchTNodeByTemplateRef(tView: TView, tNode: TNode): void {
|
||||
this.matchTNodeWithReadOption(tView, tNode, tNode.type === TNodeType.Container ? -1 : null);
|
||||
}
|
||||
|
||||
const containerValues = query.containerValues !;
|
||||
const viewValuesIdx = containerValues.indexOf(query.values);
|
||||
const removed = containerValues.splice(viewValuesIdx, 1);
|
||||
private matchTNodeByType(tView: TView, tNode: TNode, typePredicate: Type<any>): void {
|
||||
this.matchTNodeWithReadOption(
|
||||
tView, tNode, locateDirectiveOrProvider(tNode, tView, typePredicate, false, false));
|
||||
}
|
||||
|
||||
// mark a query as dirty only when removed view had matching modes
|
||||
ngDevMode && assertEqual(removed.length, 1, 'removed.length');
|
||||
if (removed[0].length) {
|
||||
query.list.setDirty();
|
||||
private matchTNodeWithReadOption(tView: TView, tNode: TNode, nodeMatchIdx: number|null): void {
|
||||
if (nodeMatchIdx !== null) {
|
||||
const read = this.metadata.read;
|
||||
if (read !== null) {
|
||||
if (read === ViewEngine_ElementRef || read === ViewContainerRef ||
|
||||
read === ViewEngine_TemplateRef && tNode.type === TNodeType.Container) {
|
||||
this.addMatch(tNode.index, -2);
|
||||
} else {
|
||||
const directiveOrProviderIdx =
|
||||
locateDirectiveOrProvider(tNode, tView, read, false, false);
|
||||
if (directiveOrProviderIdx !== null) {
|
||||
this.addMatch(tNode.index, directiveOrProviderIdx);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.addMatch(tNode.index, nodeMatchIdx);
|
||||
}
|
||||
}
|
||||
|
||||
query = query.next;
|
||||
}
|
||||
}
|
||||
|
||||
function assertViewQueryhasPointerToDeclarationContainer(query: LQuery<any>) {
|
||||
assertDefined(query.containerValues, 'View queries need to have a pointer to container values.');
|
||||
private addMatch(tNodeIdx: number, matchIdx: number) {
|
||||
if (this.matches === null) {
|
||||
this.matches = [tNodeIdx, matchIdx];
|
||||
} else {
|
||||
this.matches.push(tNodeIdx, matchIdx);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -214,7 +259,7 @@ function assertViewQueryhasPointerToDeclarationContainer(query: LQuery<any>) {
|
|||
*/
|
||||
function getIdxOfMatchingSelector(tNode: TNode, selector: string): number|null {
|
||||
const localNames = tNode.localNames;
|
||||
if (localNames) {
|
||||
if (localNames !== null) {
|
||||
for (let i = 0; i < localNames.length; i += 2) {
|
||||
if (localNames[i] === selector) {
|
||||
return localNames[i + 1] as number;
|
||||
|
@ -225,148 +270,127 @@ function getIdxOfMatchingSelector(tNode: TNode, selector: string): number|null {
|
|||
}
|
||||
|
||||
|
||||
// TODO: "read" should be an AbstractType (FW-486)
|
||||
function queryByReadToken(read: any, tNode: TNode, currentView: LView): any {
|
||||
const factoryFn = (read as any)[NG_ELEMENT_ID];
|
||||
if (typeof factoryFn === 'function') {
|
||||
return factoryFn();
|
||||
} else {
|
||||
const tView = currentView[TVIEW];
|
||||
const matchingIdx = locateDirectiveOrProvider(tNode, tView, read as Type<any>, false, false);
|
||||
if (matchingIdx !== null) {
|
||||
return getNodeInjectable(tView.data, currentView, matchingIdx, tNode as TElementNode);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function queryByTNodeType(tNode: TNode, currentView: LView): any {
|
||||
function createResultByTNodeType(tNode: TNode, currentView: LView): any {
|
||||
if (tNode.type === TNodeType.Element || tNode.type === TNodeType.ElementContainer) {
|
||||
return createElementRef(ViewEngine_ElementRef, tNode, currentView);
|
||||
}
|
||||
if (tNode.type === TNodeType.Container) {
|
||||
} else if (tNode.type === TNodeType.Container) {
|
||||
return createTemplateRef(ViewEngine_TemplateRef, ViewEngine_ElementRef, tNode, currentView);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function queryByTemplateRef(
|
||||
templateRefToken: ViewEngine_TemplateRef<any>, tNode: TNode, currentView: LView,
|
||||
read: any): any {
|
||||
const templateRefResult = (templateRefToken as any)[NG_ELEMENT_ID]();
|
||||
if (read) {
|
||||
return templateRefResult ? queryByReadToken(read, tNode, currentView) : null;
|
||||
|
||||
function createResultForNode(lView: LView, tNode: TNode, matchingIdx: number, read: any): any {
|
||||
if (matchingIdx === -1) {
|
||||
// if read token and / or strategy is not specified, detect it using appropriate tNode type
|
||||
return createResultByTNodeType(tNode, lView);
|
||||
} else if (matchingIdx === -2) {
|
||||
// read a special token from a node injector
|
||||
return createSpecialToken(lView, tNode, read);
|
||||
} else {
|
||||
// read a token
|
||||
return getNodeInjectable(lView[TVIEW].data, lView, matchingIdx, tNode as TElementNode);
|
||||
}
|
||||
return templateRefResult;
|
||||
}
|
||||
|
||||
function queryRead(tNode: TNode, currentView: LView, read: any, matchingIdx: number): any {
|
||||
if (read) {
|
||||
return queryByReadToken(read, tNode, currentView);
|
||||
function createSpecialToken(lView: LView, tNode: TNode, read: any): any {
|
||||
if (read === ViewEngine_ElementRef) {
|
||||
return createElementRef(ViewEngine_ElementRef, tNode, lView);
|
||||
} else if (read === ViewEngine_TemplateRef) {
|
||||
return createTemplateRef(ViewEngine_TemplateRef, ViewEngine_ElementRef, tNode, lView);
|
||||
} else if (read === ViewContainerRef) {
|
||||
ngDevMode && assertNodeOfPossibleTypes(
|
||||
tNode, TNodeType.Element, TNodeType.Container, TNodeType.ElementContainer);
|
||||
return createContainerRef(
|
||||
ViewContainerRef, ViewEngine_ElementRef,
|
||||
tNode as TElementNode | TContainerNode | TElementContainerNode, lView);
|
||||
} else {
|
||||
ngDevMode &&
|
||||
throwError(
|
||||
`Special token to read should be one of ElementRef, TemplateRef or ViewContainerRef but got ${stringify(read)}.`);
|
||||
}
|
||||
if (matchingIdx > -1) {
|
||||
return getNodeInjectable(
|
||||
currentView[TVIEW].data, currentView, matchingIdx, tNode as TElementNode);
|
||||
}
|
||||
// if read token and / or strategy is not specified,
|
||||
// detect it using appropriate tNode type
|
||||
return queryByTNodeType(tNode, currentView);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add query matches for a given node.
|
||||
*
|
||||
* @param query The first query in the linked list
|
||||
* @param tNode The TNode to match against queries
|
||||
* @param insertBeforeContainer Whether or not we should add matches before the last
|
||||
* container array. This mode is necessary if the query container had to be created
|
||||
* out of order (e.g. a view was created in a constructor)
|
||||
* A helper function that creates query results for a given view. This function is meant to do the
|
||||
* processing once and only once for a given view instance (a set of results for a given view
|
||||
* doesn't change).
|
||||
*/
|
||||
function add(
|
||||
query: LQuery<any>| null, tNode: TElementNode | TContainerNode | TElementContainerNode,
|
||||
insertBeforeContainer: boolean) {
|
||||
const lView = getLView();
|
||||
const tView = lView[TVIEW];
|
||||
|
||||
while (query) {
|
||||
const predicate = query.predicate;
|
||||
const type = predicate.type as any;
|
||||
if (type) {
|
||||
let result = null;
|
||||
if (type === ViewEngine_TemplateRef) {
|
||||
result = queryByTemplateRef(type, tNode, lView, predicate.read);
|
||||
function materializeViewResults<T>(lView: LView, tQuery: TQuery, queryIndex: number): (T | null)[] {
|
||||
const lQuery = lView[QUERIES] !.queries ![queryIndex];
|
||||
if (lQuery.matches === null) {
|
||||
const tViewData = lView[TVIEW].data;
|
||||
const tQueryMatches = tQuery.matches !;
|
||||
const result: T|null[] = new Array(tQueryMatches.length / 2);
|
||||
for (let i = 0; i < tQueryMatches.length; i += 2) {
|
||||
const matchedNodeIdx = tQueryMatches[i];
|
||||
if (matchedNodeIdx < 0) {
|
||||
// we at the <ng-template> marker which might have results in views created based on this
|
||||
// <ng-template> - those results will be in separate views though, so here we just leave
|
||||
// null as a placeholder
|
||||
result[i / 2] = null;
|
||||
} else {
|
||||
const matchingIdx = locateDirectiveOrProvider(tNode, tView, type, false, false);
|
||||
if (matchingIdx !== null) {
|
||||
result = queryRead(tNode, lView, predicate.read, matchingIdx);
|
||||
ngDevMode && assertDataInRange(tViewData, matchedNodeIdx);
|
||||
const tNode = tViewData[matchedNodeIdx] as TNode;
|
||||
result[i / 2] =
|
||||
createResultForNode(lView, tNode, tQueryMatches[i + 1], tQuery.metadata.read);
|
||||
}
|
||||
}
|
||||
lQuery.matches = result;
|
||||
}
|
||||
|
||||
return lQuery.matches;
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper function that collects (already materialized) query results from a tree of views,
|
||||
* starting with a provided LView.
|
||||
*/
|
||||
function collectQueryResults<T>(
|
||||
lView: LView, tQuery: TQuery, queryIndex: number, result: T[]): T[] {
|
||||
ngDevMode &&
|
||||
assertDefined(
|
||||
tQuery.matches, 'Query results can only be collected for queries with existing matches.');
|
||||
|
||||
const tQueryMatches = tQuery.matches !;
|
||||
const lViewResults = materializeViewResults<T>(lView, tQuery, queryIndex);
|
||||
|
||||
for (let i = 0; i < tQueryMatches.length; i += 2) {
|
||||
const tNodeIdx = tQueryMatches[i];
|
||||
if (tNodeIdx > 0) {
|
||||
const viewResult = lViewResults[i / 2];
|
||||
ngDevMode && assertDefined(viewResult, 'materialized query result should be defined');
|
||||
result.push(viewResult as T);
|
||||
} else {
|
||||
const childQueryIndex = tQueryMatches[i + 1];
|
||||
|
||||
const declarationLContainer = lView[-tNodeIdx] as LContainer;
|
||||
ngDevMode && assertLContainer(declarationLContainer);
|
||||
|
||||
// collect matches for views inserted in this container
|
||||
for (let i = CONTAINER_HEADER_OFFSET; i < declarationLContainer.length; i++) {
|
||||
const embeddedLView = declarationLContainer[i];
|
||||
if (embeddedLView[DECLARATION_LCONTAINER] === embeddedLView[PARENT]) {
|
||||
const tquery = getTQuery(embeddedLView[TVIEW], childQueryIndex);
|
||||
if (tquery.matches !== null) {
|
||||
collectQueryResults(embeddedLView, tquery, childQueryIndex, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result !== null) {
|
||||
addMatch(query, result, insertBeforeContainer);
|
||||
}
|
||||
} else {
|
||||
const selector = predicate.selector !;
|
||||
for (let i = 0; i < selector.length; i++) {
|
||||
const matchingIdx = getIdxOfMatchingSelector(tNode, selector[i]);
|
||||
if (matchingIdx !== null) {
|
||||
const result = queryRead(tNode, lView, predicate.read, matchingIdx);
|
||||
if (result !== null) {
|
||||
addMatch(query, result, insertBeforeContainer);
|
||||
|
||||
// collect matches for views created from this declaration container and inserted into
|
||||
// different containers
|
||||
if (declarationLContainer[MOVED_VIEWS] !== null) {
|
||||
for (let embeddedLView of declarationLContainer[MOVED_VIEWS] !) {
|
||||
const tquery = getTQuery(embeddedLView[TVIEW], childQueryIndex);
|
||||
if (tquery.matches !== null) {
|
||||
collectQueryResults(embeddedLView, tquery, childQueryIndex, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
query = query.next;
|
||||
}
|
||||
}
|
||||
|
||||
function addMatch(query: LQuery<any>, matchingValue: any, insertBeforeViewMatches: boolean): void {
|
||||
// Views created in constructors may have their container values created too early. In this case,
|
||||
// ensure template node results are unshifted before container results. Otherwise, results inside
|
||||
// embedded views will appear before results on parent template nodes when flattened.
|
||||
insertBeforeViewMatches ? query.values.unshift(matchingValue) : query.values.push(matchingValue);
|
||||
query.list.setDirty();
|
||||
}
|
||||
|
||||
function createPredicate<T>(predicate: Type<T>| string[], read: Type<T>| null): QueryPredicate<T> {
|
||||
const isArray = Array.isArray(predicate);
|
||||
return {
|
||||
type: isArray ? null : predicate as Type<T>,
|
||||
selector: isArray ? predicate as string[] : null,
|
||||
read: read
|
||||
};
|
||||
}
|
||||
|
||||
function createLQuery<T>(
|
||||
previous: LQuery<any>| null, queryList: QueryList<T>, predicate: Type<T>| string[],
|
||||
read: Type<T>| null): LQuery<T> {
|
||||
return new LQuery(
|
||||
previous, queryList, createPredicate(predicate, read),
|
||||
(queryList as any as QueryList_<T>)._valuesTree, null);
|
||||
}
|
||||
|
||||
type QueryList_<T> = QueryList<T>& {_valuesTree: any[], _static: boolean};
|
||||
|
||||
/**
|
||||
* Creates a QueryList and stores it in LView's collection of active queries (LQueries).
|
||||
*
|
||||
* @param predicate The type for which the query will search
|
||||
* @param descend Whether or not to descend into children
|
||||
* @param read What to save in the query
|
||||
* @returns QueryList<T>
|
||||
*/
|
||||
function createQueryListInLView<T>(
|
||||
// TODO: "read" should be an AbstractType (FW-486)
|
||||
lView: LView, predicate: Type<any>| string[], descend: boolean, read: any, isStatic: boolean,
|
||||
nodeIndex: number): QueryList<T> {
|
||||
ngDevMode && assertPreviousIsParent(getIsParent());
|
||||
const queryList = new QueryList<T>() as QueryList_<T>;
|
||||
const queries = lView[QUERIES] || (lView[QUERIES] = new LQueries_(null, null, null, nodeIndex));
|
||||
queryList._valuesTree = [];
|
||||
queryList._static = isStatic;
|
||||
queries.track(queryList, predicate, descend, read);
|
||||
storeCleanupWithContext(lView, queryList, queryList.destroy);
|
||||
return queryList;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -379,15 +403,24 @@ function createQueryListInLView<T>(
|
|||
* @codeGenApi
|
||||
*/
|
||||
export function ɵɵqueryRefresh(queryList: QueryList<any>): boolean {
|
||||
const queryListImpl = (queryList as any as QueryList_<any>);
|
||||
const creationMode = isCreationMode();
|
||||
const lView = getLView();
|
||||
const queryIndex = getCurrentQueryIndex();
|
||||
|
||||
// if creation mode and static or update mode and not static
|
||||
if (queryList.dirty && creationMode === queryListImpl._static) {
|
||||
queryList.reset(queryListImpl._valuesTree || []);
|
||||
queryList.notifyOnChanges();
|
||||
setCurrentQueryIndex(queryIndex + 1);
|
||||
|
||||
const tQuery = getTQuery(lView[TVIEW], queryIndex);
|
||||
if (queryList.dirty && (isCreationMode() === tQuery.metadata.isStatic)) {
|
||||
if (tQuery.matches === null) {
|
||||
queryList.reset([]);
|
||||
} else {
|
||||
const result = tQuery.crossesNgTemplate ? collectQueryResults(lView, tQuery, queryIndex, []) :
|
||||
materializeViewResults(lView, tQuery, queryIndex);
|
||||
queryList.reset(result);
|
||||
queryList.notifyOnChanges();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -401,12 +434,8 @@ export function ɵɵqueryRefresh(queryList: QueryList<any>): boolean {
|
|||
* @codeGenApi
|
||||
*/
|
||||
export function ɵɵstaticViewQuery<T>(
|
||||
// TODO(FW-486): "read" should be an AbstractType
|
||||
predicate: Type<any>| string[], descend: boolean, read: any): void {
|
||||
const lView = getLView();
|
||||
const tView = lView[TVIEW];
|
||||
viewQueryInternal(lView, tView, predicate, descend, read, true);
|
||||
tView.staticViewQueries = true;
|
||||
viewQueryInternal(getLView(), predicate, descend, read, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -415,41 +444,33 @@ export function ɵɵstaticViewQuery<T>(
|
|||
* @param predicate The type for which the query will search
|
||||
* @param descend Whether or not to descend into children
|
||||
* @param read What to save in the query
|
||||
* @returns QueryList<T>
|
||||
*
|
||||
* @codeGenApi
|
||||
*/
|
||||
export function ɵɵviewQuery<T>(
|
||||
// TODO(FW-486): "read" should be an AbstractType
|
||||
predicate: Type<any>| string[], descend: boolean, read: any): QueryList<T> {
|
||||
const lView = getLView();
|
||||
const tView = lView[TVIEW];
|
||||
return viewQueryInternal(lView, tView, predicate, descend, read, false);
|
||||
export function ɵɵviewQuery<T>(predicate: Type<any>| string[], descend: boolean, read: any): void {
|
||||
viewQueryInternal(getLView(), predicate, descend, read, false);
|
||||
}
|
||||
|
||||
function viewQueryInternal<T>(
|
||||
lView: LView, tView: TView, predicate: Type<any>| string[], descend: boolean, read: any,
|
||||
isStatic: boolean): QueryList<T> {
|
||||
lView: LView, predicate: Type<any>| string[], descend: boolean, read: any,
|
||||
isStatic: boolean): void {
|
||||
const tView = lView[TVIEW];
|
||||
if (tView.firstTemplatePass) {
|
||||
tView.expandoStartIndex++;
|
||||
createTQuery(tView, new TQueryMetadata_(predicate, descend, read, isStatic), -1);
|
||||
if (isStatic) {
|
||||
tView.staticViewQueries = true;
|
||||
}
|
||||
}
|
||||
const index = getCurrentQueryIndex();
|
||||
const queryList: QueryList<T> =
|
||||
createQueryListInLView<T>(lView, predicate, descend, read, isStatic, -1);
|
||||
store(index - HEADER_OFFSET, queryList);
|
||||
setCurrentQueryIndex(index + 1);
|
||||
return queryList;
|
||||
createLQuery<T>(lView);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads current View Query and moves the pointer/index to the next View Query in LView.
|
||||
* Loads a QueryList corresponding to the current view query.
|
||||
*
|
||||
* @codeGenApi
|
||||
*/
|
||||
export function ɵɵloadViewQuery<T>(): T {
|
||||
const index = getCurrentQueryIndex();
|
||||
setCurrentQueryIndex(index + 1);
|
||||
return loadInternal<T>(getLView(), index - HEADER_OFFSET);
|
||||
export function ɵɵloadViewQuery<T>(): QueryList<T> {
|
||||
return loadQueryInternal<T>(getLView(), getCurrentQueryIndex());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -465,33 +486,9 @@ export function ɵɵloadViewQuery<T>(): T {
|
|||
* @codeGenApi
|
||||
*/
|
||||
export function ɵɵcontentQuery<T>(
|
||||
directiveIndex: number, predicate: Type<any>| string[], descend: boolean,
|
||||
// TODO(FW-486): "read" should be an AbstractType
|
||||
read: any): QueryList<T> {
|
||||
const lView = getLView();
|
||||
const tView = lView[TVIEW];
|
||||
const tNode = getPreviousOrParentTNode();
|
||||
return contentQueryInternal(
|
||||
lView, tView, directiveIndex, predicate, descend, read, false, tNode.index);
|
||||
}
|
||||
|
||||
function contentQueryInternal<T>(
|
||||
lView: LView, tView: TView, directiveIndex: number, predicate: Type<any>| string[],
|
||||
descend: boolean,
|
||||
// TODO(FW-486): "read" should be an AbstractType
|
||||
read: any, isStatic: boolean, nodeIndex: number): QueryList<T> {
|
||||
const contentQuery: QueryList<T> =
|
||||
createQueryListInLView<T>(lView, predicate, descend, read, isStatic, nodeIndex);
|
||||
(lView[CONTENT_QUERIES] || (lView[CONTENT_QUERIES] = [])).push(contentQuery);
|
||||
if (tView.firstTemplatePass) {
|
||||
const tViewContentQueries = tView.contentQueries || (tView.contentQueries = []);
|
||||
const lastSavedDirectiveIndex =
|
||||
tView.contentQueries.length ? tView.contentQueries[tView.contentQueries.length - 1] : -1;
|
||||
if (directiveIndex !== lastSavedDirectiveIndex) {
|
||||
tViewContentQueries.push(directiveIndex);
|
||||
}
|
||||
}
|
||||
return contentQuery;
|
||||
directiveIndex: number, predicate: Type<any>| string[], descend: boolean, read: any): void {
|
||||
contentQueryInternal(
|
||||
getLView(), predicate, descend, read, false, getPreviousOrParentTNode(), directiveIndex);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -507,29 +504,65 @@ function contentQueryInternal<T>(
|
|||
* @codeGenApi
|
||||
*/
|
||||
export function ɵɵstaticContentQuery<T>(
|
||||
directiveIndex: number, predicate: Type<any>| string[], descend: boolean,
|
||||
// TODO(FW-486): "read" should be an AbstractType
|
||||
read: any): void {
|
||||
const lView = getLView();
|
||||
directiveIndex: number, predicate: Type<any>| string[], descend: boolean, read: any): void {
|
||||
contentQueryInternal(
|
||||
getLView(), predicate, descend, read, true, getPreviousOrParentTNode(), directiveIndex);
|
||||
}
|
||||
|
||||
function contentQueryInternal<T>(
|
||||
lView: LView, predicate: Type<any>| string[], descend: boolean, read: any, isStatic: boolean,
|
||||
tNode: TNode, directiveIndex: number): void {
|
||||
const tView = lView[TVIEW];
|
||||
const tNode = getPreviousOrParentTNode();
|
||||
contentQueryInternal(lView, tView, directiveIndex, predicate, descend, read, true, tNode.index);
|
||||
tView.staticContentQueries = true;
|
||||
if (tView.firstTemplatePass) {
|
||||
createTQuery(tView, new TQueryMetadata_(predicate, descend, read, isStatic), tNode.index);
|
||||
saveContentQueryAndDirectiveIndex(tView, directiveIndex);
|
||||
if (isStatic) {
|
||||
tView.staticContentQueries = true;
|
||||
}
|
||||
}
|
||||
|
||||
createLQuery<T>(lView);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a QueryList corresponding to the current content query.
|
||||
*
|
||||
* @codeGenApi
|
||||
*/
|
||||
export function ɵɵloadContentQuery<T>(): QueryList<T> {
|
||||
const lView = getLView();
|
||||
ngDevMode &&
|
||||
assertDefined(
|
||||
lView[CONTENT_QUERIES], 'Content QueryList array should be defined if reading a query.');
|
||||
|
||||
const index = getCurrentQueryIndex();
|
||||
ngDevMode && assertDataInRange(lView[CONTENT_QUERIES] !, index);
|
||||
|
||||
setCurrentQueryIndex(index + 1);
|
||||
return lView[CONTENT_QUERIES] ![index];
|
||||
return loadQueryInternal<T>(getLView(), getCurrentQueryIndex());
|
||||
}
|
||||
|
||||
function loadQueryInternal<T>(lView: LView, queryIndex: number): QueryList<T> {
|
||||
ngDevMode &&
|
||||
assertDefined(lView[QUERIES], 'LQueries should be defined when trying to load a query');
|
||||
ngDevMode && assertDataInRange(lView[QUERIES] !.queries, queryIndex);
|
||||
return lView[QUERIES] !.queries[queryIndex].queryList;
|
||||
}
|
||||
|
||||
function createLQuery<T>(lView: LView) {
|
||||
const queryList = new QueryList<T>();
|
||||
storeCleanupWithContext(lView, queryList, queryList.destroy);
|
||||
|
||||
if (lView[QUERIES] === null) lView[QUERIES] = new LQueries_();
|
||||
lView[QUERIES] !.queries.push(new LQuery_(queryList));
|
||||
}
|
||||
|
||||
function createTQuery(tView: TView, metadata: TQueryMetadata, nodeIndex: number): void {
|
||||
if (tView.queries === null) tView.queries = new TQueries_();
|
||||
tView.queries.track(new TQuery_(metadata, nodeIndex));
|
||||
}
|
||||
|
||||
function saveContentQueryAndDirectiveIndex(tView: TView, directiveIndex: number) {
|
||||
const tViewContentQueries = tView.contentQueries || (tView.contentQueries = []);
|
||||
const lastSavedDirectiveIndex =
|
||||
tView.contentQueries.length ? tViewContentQueries[tViewContentQueries.length - 1] : -1;
|
||||
if (directiveIndex !== lastSavedDirectiveIndex) {
|
||||
tViewContentQueries.push(tView.queries !.length - 1, directiveIndex);
|
||||
}
|
||||
}
|
||||
|
||||
function getTQuery(tView: TView, index: number): TQuery {
|
||||
ngDevMode && assertDefined(tView.queries, 'TQueries must be defined to retrieve a TQuery');
|
||||
return tView.queries !.getByIndex(index);
|
||||
}
|
||||
|
|
|
@ -17,13 +17,16 @@ import {EmbeddedViewRef as viewEngine_EmbeddedViewRef, ViewRef as viewEngine_Vie
|
|||
import {Renderer2} from '../render/api';
|
||||
import {assertDefined, assertGreaterThan, assertLessThan} from '../util/assert';
|
||||
|
||||
import {assertLContainer} from './assert';
|
||||
import {NodeInjector, getParentInjectorLocation} from './di';
|
||||
import {addToViewTree, createEmbeddedViewAndNode, createLContainer, renderEmbeddedTemplate} from './instructions/shared';
|
||||
import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer, VIEW_REFS} from './interfaces/container';
|
||||
import {TContainerNode, TElementContainerNode, TElementNode, TNode, TNodeType, TViewNode} from './interfaces/node';
|
||||
import {RComment, RElement, isProceduralRenderer} from './interfaces/renderer';
|
||||
|
||||
import {isComponent, isLContainer, isLView, isRootView} from './interfaces/type_checks';
|
||||
import {CONTEXT, LView, QUERIES, RENDERER, TView, T_HOST} from './interfaces/view';
|
||||
import {CONTEXT, DECLARATION_LCONTAINER, LView, QUERIES, RENDERER, TView, T_HOST} from './interfaces/view';
|
||||
|
||||
import {assertNodeOfPossibleTypes} from './node_assert';
|
||||
import {addRemoveViewFromContainer, appendChild, detachView, getBeforeNodeForView, insertView, nativeInsertBefore, nativeNextSibling, nativeParentNode, removeView} from './node_manipulation';
|
||||
import {getParentInjectorTNode} from './node_util';
|
||||
|
@ -66,9 +69,8 @@ export function createElementRef(
|
|||
}
|
||||
|
||||
let R3TemplateRef: {
|
||||
new (
|
||||
_declarationParentView: LView, elementRef: ViewEngine_ElementRef, _tView: TView,
|
||||
_hostLContainer: LContainer, _injectorIndex: number): ViewEngine_TemplateRef<any>
|
||||
new (_declarationParentView: LView, hostTNode: TContainerNode, elementRef: ViewEngine_ElementRef):
|
||||
ViewEngine_TemplateRef<any>
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -88,9 +90,9 @@ export function injectTemplateRef<T>(
|
|||
*
|
||||
* @param TemplateRefToken The TemplateRef type
|
||||
* @param ElementRefToken The ElementRef type
|
||||
* @param hostTNode The node that is requesting a TemplateRef
|
||||
* @param hostTNode The node on which a TemplateRef is requested
|
||||
* @param hostView The view to which the node belongs
|
||||
* @returns The TemplateRef instance to use
|
||||
* @returns The TemplateRef instance or null if we can't create a TemplateRef on a given node type
|
||||
*/
|
||||
export function createTemplateRef<T>(
|
||||
TemplateRefToken: typeof ViewEngine_TemplateRef, ElementRefToken: typeof ViewEngine_ElementRef,
|
||||
|
@ -99,27 +101,27 @@ export function createTemplateRef<T>(
|
|||
// TODO: Fix class name, should be TemplateRef, but there appears to be a rollup bug
|
||||
R3TemplateRef = class TemplateRef_<T> extends TemplateRefToken<T> {
|
||||
constructor(
|
||||
private _declarationParentView: LView, readonly elementRef: ViewEngine_ElementRef,
|
||||
private _tView: TView, private _hostLContainer: LContainer,
|
||||
private _injectorIndex: number) {
|
||||
private _declarationView: LView, private _declarationTContainer: TContainerNode,
|
||||
readonly elementRef: ViewEngine_ElementRef) {
|
||||
super();
|
||||
}
|
||||
|
||||
createEmbeddedView(context: T, container?: LContainer, index?: number):
|
||||
viewEngine_EmbeddedViewRef<T> {
|
||||
const currentQueries = this._declarationParentView[QUERIES];
|
||||
// Query container may be missing if this view was created in a directive
|
||||
// constructor. Create it now to avoid losing results in embedded views.
|
||||
if (currentQueries && this._hostLContainer[QUERIES] == null) {
|
||||
this._hostLContainer[QUERIES] = currentQueries !.container();
|
||||
}
|
||||
createEmbeddedView(context: T): viewEngine_EmbeddedViewRef<T> {
|
||||
const embeddedTView = this._declarationTContainer.tViews as TView;
|
||||
const lView = createEmbeddedViewAndNode(
|
||||
this._tView, context, this._declarationParentView, this._hostLContainer[QUERIES],
|
||||
this._injectorIndex);
|
||||
if (container) {
|
||||
insertView(lView, container, index !);
|
||||
embeddedTView, context, this._declarationView,
|
||||
this._declarationTContainer.injectorIndex);
|
||||
|
||||
const declarationLContainer = this._declarationView[this._declarationTContainer.index];
|
||||
ngDevMode && assertLContainer(declarationLContainer);
|
||||
lView[DECLARATION_LCONTAINER] = declarationLContainer;
|
||||
|
||||
const declarationViewLQueries = this._declarationView[QUERIES];
|
||||
if (declarationViewLQueries !== null) {
|
||||
lView[QUERIES] = declarationViewLQueries.createEmbeddedView(embeddedTView);
|
||||
}
|
||||
renderEmbeddedTemplate(lView, this._tView, context);
|
||||
|
||||
renderEmbeddedTemplate(lView, embeddedTView, context);
|
||||
const viewRef = new ViewRef(lView, context, -1);
|
||||
viewRef._tViewNode = lView[T_HOST] as TViewNode;
|
||||
return viewRef;
|
||||
|
@ -128,11 +130,10 @@ export function createTemplateRef<T>(
|
|||
}
|
||||
|
||||
if (hostTNode.type === TNodeType.Container) {
|
||||
const hostContainer: LContainer = hostView[hostTNode.index];
|
||||
ngDevMode && assertDefined(hostTNode.tViews, 'TView must be allocated');
|
||||
return new R3TemplateRef(
|
||||
hostView, createElementRef(ElementRefToken, hostTNode, hostView), hostTNode.tViews as TView,
|
||||
hostContainer, hostTNode.injectorIndex);
|
||||
hostView, hostTNode as TContainerNode,
|
||||
createElementRef(ElementRefToken, hostTNode, hostView));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
@ -218,12 +219,8 @@ export function createContainerRef(
|
|||
|
||||
createEmbeddedView<C>(templateRef: ViewEngine_TemplateRef<C>, context?: C, index?: number):
|
||||
viewEngine_EmbeddedViewRef<C> {
|
||||
this.allocateContainerIfNeeded();
|
||||
const adjustedIdx = this._adjustIndex(index);
|
||||
const viewRef = (templateRef as any)
|
||||
.createEmbeddedView(context || <any>{}, this._lContainer, adjustedIdx);
|
||||
(viewRef as ViewRef<any>).attachToViewContainerRef(this);
|
||||
this._lContainer[VIEW_REFS] !.splice(adjustedIdx, 0, viewRef);
|
||||
const viewRef = templateRef.createEmbeddedView(context || <any>{});
|
||||
this.insert(viewRef, index);
|
||||
return viewRef;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
*/
|
||||
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {Component, ContentChild, ContentChildren, Directive, ElementRef, Input, QueryList, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef} from '@angular/core';
|
||||
import {Component, ContentChild, ContentChildren, Directive, ElementRef, Input, QueryList, TemplateRef, Type, ViewChild, ViewChildren, ViewContainerRef, forwardRef} from '@angular/core';
|
||||
import {TestBed} from '@angular/core/testing';
|
||||
import {By} from '@angular/platform-browser';
|
||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
|
@ -786,6 +786,56 @@ describe('query logic', () => {
|
|||
expect(queryList.last.nativeElement.id).toBe('c');
|
||||
});
|
||||
|
||||
it('should support a mix of content queries from the declaration and embedded view', () => {
|
||||
@Directive({selector: '[query-for-lots-of-content]'})
|
||||
class QueryForLotsOfContent {
|
||||
@ContentChildren('foo', {descendants: true}) foos1 !: QueryList<ElementRef>;
|
||||
@ContentChildren('foo', {descendants: true}) foos2 !: QueryList<ElementRef>;
|
||||
}
|
||||
|
||||
@Directive({selector: '[query-for-content]'})
|
||||
class QueryForContent {
|
||||
@ContentChildren('foo') foos !: QueryList<ElementRef>;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'test-comp',
|
||||
template: `
|
||||
<div query-for-lots-of-content>
|
||||
<ng-template ngFor let-item [ngForOf]="items">
|
||||
<div query-for-content>
|
||||
<span #foo></span>
|
||||
</div>
|
||||
</ng-template>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
class TestComponent {
|
||||
items = [1, 2];
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule(
|
||||
{declarations: [TestComponent, QueryForContent, QueryForLotsOfContent]});
|
||||
|
||||
const fixture = TestBed.createComponent(TestComponent);
|
||||
fixture.detectChanges();
|
||||
|
||||
const lotsOfContentEl = fixture.debugElement.query(By.directive(QueryForLotsOfContent));
|
||||
const lotsOfContentInstance = lotsOfContentEl.injector.get(QueryForLotsOfContent);
|
||||
|
||||
const contentEl = fixture.debugElement.query(By.directive(QueryForContent));
|
||||
const contentInstance = contentEl.injector.get(QueryForContent);
|
||||
|
||||
expect(lotsOfContentInstance.foos1.length).toBe(2);
|
||||
expect(lotsOfContentInstance.foos2.length).toBe(2);
|
||||
expect(contentInstance.foos.length).toBe(1);
|
||||
|
||||
fixture.componentInstance.items = [];
|
||||
fixture.detectChanges();
|
||||
expect(lotsOfContentInstance.foos1.length).toBe(0);
|
||||
expect(lotsOfContentInstance.foos2.length).toBe(0);
|
||||
});
|
||||
|
||||
// https://stackblitz.com/edit/angular-rrmmuf?file=src/app/app.component.ts
|
||||
it('should report results when different instances of TemplateRef are inserted into one ViewContainerRefs',
|
||||
() => {
|
||||
|
@ -864,12 +914,11 @@ describe('query logic', () => {
|
|||
|
||||
// https://stackblitz.com/edit/angular-7vvo9j?file=src%2Fapp%2Fapp.component.ts
|
||||
// https://stackblitz.com/edit/angular-xzwp6n
|
||||
onlyInIvy('FW-1318: QueryList entries are ordered differently in Ivy.')
|
||||
.it('should report results when the same TemplateRef is inserted into different ViewContainerRefs',
|
||||
() => {
|
||||
@Component({
|
||||
selector: 'test-comp',
|
||||
template: `
|
||||
it('should report results when the same TemplateRef is inserted into different ViewContainerRefs',
|
||||
() => {
|
||||
@Component({
|
||||
selector: 'test-comp',
|
||||
template: `
|
||||
<ng-template #tpl let-idx="idx" let-container_idx="container_idx">
|
||||
<div #foo [id]="'foo_' + container_idx + '_' + idx"></div>
|
||||
</ng-template>
|
||||
|
@ -877,44 +926,44 @@ describe('query logic', () => {
|
|||
<ng-template vc #vi0="vc"></ng-template>
|
||||
<ng-template vc #vi1="vc"></ng-template>
|
||||
`,
|
||||
})
|
||||
class TestComponent {
|
||||
@ViewChild('tpl', {static: false}) tpl !: TemplateRef<any>;
|
||||
@ViewChild('vi0', {static: false}) vi0 !: ViewContainerManipulatorDirective;
|
||||
@ViewChild('vi1', {static: false}) vi1 !: ViewContainerManipulatorDirective;
|
||||
@ViewChildren('foo') query !: QueryList<any>;
|
||||
}
|
||||
})
|
||||
class TestComponent {
|
||||
@ViewChild('tpl', {static: false}) tpl !: TemplateRef<any>;
|
||||
@ViewChild('vi0', {static: false}) vi0 !: ViewContainerManipulatorDirective;
|
||||
@ViewChild('vi1', {static: false}) vi1 !: ViewContainerManipulatorDirective;
|
||||
@ViewChildren('foo') query !: QueryList<any>;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule(
|
||||
{declarations: [ViewContainerManipulatorDirective, TestComponent]});
|
||||
const fixture = TestBed.createComponent(TestComponent);
|
||||
fixture.detectChanges();
|
||||
TestBed.configureTestingModule(
|
||||
{declarations: [ViewContainerManipulatorDirective, TestComponent]});
|
||||
const fixture = TestBed.createComponent(TestComponent);
|
||||
fixture.detectChanges();
|
||||
|
||||
const queryList = fixture.componentInstance.query;
|
||||
const {tpl, vi0, vi1} = fixture.componentInstance;
|
||||
const queryList = fixture.componentInstance.query;
|
||||
const {tpl, vi0, vi1} = fixture.componentInstance;
|
||||
|
||||
expect(queryList.length).toBe(0);
|
||||
expect(queryList.length).toBe(0);
|
||||
|
||||
vi0.insertTpl(tpl !, {idx: 0, container_idx: 0}, 0);
|
||||
vi1.insertTpl(tpl !, {idx: 0, container_idx: 1}, 0);
|
||||
fixture.detectChanges();
|
||||
vi0.insertTpl(tpl !, {idx: 0, container_idx: 0}, 0);
|
||||
vi1.insertTpl(tpl !, {idx: 0, container_idx: 1}, 0);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(queryList.length).toBe(2);
|
||||
let qListArr = queryList.toArray();
|
||||
expect(qListArr[0].nativeElement.getAttribute('id')).toBe('foo_1_0');
|
||||
expect(qListArr[1].nativeElement.getAttribute('id')).toBe('foo_0_0');
|
||||
expect(queryList.length).toBe(2);
|
||||
let qListArr = queryList.toArray();
|
||||
expect(qListArr[0].nativeElement.getAttribute('id')).toBe('foo_0_0');
|
||||
expect(qListArr[1].nativeElement.getAttribute('id')).toBe('foo_1_0');
|
||||
|
||||
vi0.remove();
|
||||
fixture.detectChanges();
|
||||
vi0.remove();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(queryList.length).toBe(1);
|
||||
qListArr = queryList.toArray();
|
||||
expect(qListArr[0].nativeElement.getAttribute('id')).toBe('foo_1_0');
|
||||
expect(queryList.length).toBe(1);
|
||||
qListArr = queryList.toArray();
|
||||
expect(qListArr[0].nativeElement.getAttribute('id')).toBe('foo_1_0');
|
||||
|
||||
vi1.remove();
|
||||
fixture.detectChanges();
|
||||
expect(queryList.length).toBe(0);
|
||||
});
|
||||
vi1.remove();
|
||||
fixture.detectChanges();
|
||||
expect(queryList.length).toBe(0);
|
||||
});
|
||||
|
||||
// https://stackblitz.com/edit/angular-wpd6gv?file=src%2Fapp%2Fapp.component.ts
|
||||
it('should report results from views inserted in a lifecycle hook', () => {
|
||||
|
@ -953,6 +1002,96 @@ describe('query logic', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('non-regression', () => {
|
||||
|
||||
it('should query by provider super-type in an embedded view', () => {
|
||||
|
||||
@Directive({selector: '[child]'})
|
||||
class Child {
|
||||
}
|
||||
|
||||
@Directive({selector: '[parent]', providers: [{provide: Child, useExisting: Parent}]})
|
||||
class Parent extends Child {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'test-cmpt',
|
||||
template:
|
||||
`<ng-template [ngIf]="true"><ng-template [ngIf]="true"><div parent></div></ng-template></ng-template>`
|
||||
})
|
||||
class TestCmpt {
|
||||
@ViewChildren(Child) instances !: QueryList<Child>;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [TestCmpt, Parent, Child]});
|
||||
const fixture = TestBed.createComponent(TestCmpt);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.componentInstance.instances.length).toBe(1);
|
||||
});
|
||||
|
||||
it('should flatten multi-provider results', () => {
|
||||
|
||||
class MyClass {}
|
||||
|
||||
@Component({
|
||||
selector: 'with-multi-provider',
|
||||
template: '',
|
||||
providers:
|
||||
[{provide: MyClass, useExisting: forwardRef(() => WithMultiProvider), multi: true}]
|
||||
})
|
||||
class WithMultiProvider {
|
||||
}
|
||||
|
||||
@Component({selector: 'test-cmpt', template: `<with-multi-provider></with-multi-provider>`})
|
||||
class TestCmpt {
|
||||
@ViewChildren(MyClass) queryResults !: QueryList<WithMultiProvider>;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [TestCmpt, WithMultiProvider]});
|
||||
const fixture = TestBed.createComponent(TestCmpt);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.componentInstance.queryResults.length).toBe(1);
|
||||
expect(fixture.componentInstance.queryResults.first).toBeAnInstanceOf(WithMultiProvider);
|
||||
});
|
||||
|
||||
it('should flatten multi-provider results when crossing ng-template', () => {
|
||||
|
||||
class MyClass {}
|
||||
|
||||
@Component({
|
||||
selector: 'with-multi-provider',
|
||||
template: '',
|
||||
providers:
|
||||
[{provide: MyClass, useExisting: forwardRef(() => WithMultiProvider), multi: true}]
|
||||
})
|
||||
class WithMultiProvider {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'test-cmpt',
|
||||
template: `
|
||||
<ng-template [ngIf]="true"><with-multi-provider></with-multi-provider></ng-template>
|
||||
<with-multi-provider></with-multi-provider>
|
||||
`
|
||||
})
|
||||
class TestCmpt {
|
||||
@ViewChildren(MyClass) queryResults !: QueryList<WithMultiProvider>;
|
||||
}
|
||||
|
||||
TestBed.configureTestingModule({declarations: [TestCmpt, WithMultiProvider]});
|
||||
const fixture = TestBed.createComponent(TestCmpt);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(fixture.componentInstance.queryResults.length).toBe(2);
|
||||
expect(fixture.componentInstance.queryResults.first).toBeAnInstanceOf(WithMultiProvider);
|
||||
expect(fixture.componentInstance.queryResults.last).toBeAnInstanceOf(WithMultiProvider);
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
function initWithTemplate(compType: Type<any>, template: string) {
|
||||
|
|
|
@ -110,9 +110,6 @@
|
|||
{
|
||||
"name": "PREORDER_HOOK_FLAGS"
|
||||
},
|
||||
{
|
||||
"name": "QUERIES"
|
||||
},
|
||||
{
|
||||
"name": "RENDERER"
|
||||
},
|
||||
|
|
|
@ -38,6 +38,9 @@
|
|||
{
|
||||
"name": "ChangeDetectionStrategy"
|
||||
},
|
||||
{
|
||||
"name": "DECLARATION_LCONTAINER"
|
||||
},
|
||||
{
|
||||
"name": "DECLARATION_VIEW"
|
||||
},
|
||||
|
@ -116,6 +119,9 @@
|
|||
{
|
||||
"name": "MONKEY_PATCH_KEY_NAME"
|
||||
},
|
||||
{
|
||||
"name": "MOVED_VIEWS"
|
||||
},
|
||||
{
|
||||
"name": "NATIVE"
|
||||
},
|
||||
|
@ -437,9 +443,6 @@
|
|||
{
|
||||
"name": "addRemoveViewFromContainer"
|
||||
},
|
||||
{
|
||||
"name": "addTContainerToQueries"
|
||||
},
|
||||
{
|
||||
"name": "addToViewTree"
|
||||
},
|
||||
|
@ -602,6 +605,9 @@
|
|||
{
|
||||
"name": "destroyViewTree"
|
||||
},
|
||||
{
|
||||
"name": "detachMovedView"
|
||||
},
|
||||
{
|
||||
"name": "detachView"
|
||||
},
|
||||
|
@ -1367,6 +1373,9 @@
|
|||
{
|
||||
"name": "trackByIdentity"
|
||||
},
|
||||
{
|
||||
"name": "trackMovedView"
|
||||
},
|
||||
{
|
||||
"name": "unwrapRNode"
|
||||
},
|
||||
|
|
|
@ -1344,378 +1344,6 @@ describe('query', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('view boundaries', () => {
|
||||
|
||||
describe('JS blocks', () => {
|
||||
|
||||
it('should report results in embedded views', () => {
|
||||
let firstEl;
|
||||
/**
|
||||
* % if (exp) {
|
||||
* <div #foo></div>
|
||||
* % }
|
||||
* class Cmpt {
|
||||
* @ViewChildren('foo') query;
|
||||
* }
|
||||
*/
|
||||
const Cmpt = createComponent(
|
||||
'cmpt',
|
||||
function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵcontainer(0);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(0);
|
||||
{
|
||||
if (ctx.exp) {
|
||||
let rf1 = ɵɵembeddedViewStart(1, 2, 0);
|
||||
{
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
ɵɵelement(0, 'div', null, ['foo', '']);
|
||||
firstEl = getNativeByIndex(0, getLView());
|
||||
}
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
},
|
||||
1, 0, [], [],
|
||||
function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵviewQuery(['foo'], true, null);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
let tmp: any;
|
||||
ɵɵqueryRefresh(tmp = ɵɵloadViewQuery<QueryList<any>>()) &&
|
||||
(ctx.query = tmp as QueryList<any>);
|
||||
}
|
||||
});
|
||||
|
||||
const cmptInstance = renderComponent(Cmpt);
|
||||
const qList = (cmptInstance.query as any);
|
||||
expect(qList.length).toBe(0);
|
||||
|
||||
cmptInstance.exp = true;
|
||||
detectChanges(cmptInstance);
|
||||
expect(qList.length).toBe(1);
|
||||
expect(qList.first.nativeElement).toBe(firstEl);
|
||||
|
||||
cmptInstance.exp = false;
|
||||
detectChanges(cmptInstance);
|
||||
expect(qList.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should add results from embedded views in the correct order - views and elements mix',
|
||||
() => {
|
||||
let firstEl, lastEl, viewEl;
|
||||
/**
|
||||
* <span #foo></span>
|
||||
* % if (exp) {
|
||||
* <div #foo></div>
|
||||
* % }
|
||||
* <span #foo></span>
|
||||
* class Cmpt {
|
||||
* @ViewChildren('foo') query;
|
||||
* }
|
||||
*/
|
||||
const Cmpt = createComponent(
|
||||
'cmpt',
|
||||
function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵelement(0, 'span', null, ['foo', '']);
|
||||
firstEl = getNativeByIndex(0, getLView());
|
||||
ɵɵcontainer(2);
|
||||
ɵɵelement(3, 'span', null, ['foo', '']);
|
||||
lastEl = getNativeByIndex(3, getLView());
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(2);
|
||||
{
|
||||
if (ctx.exp) {
|
||||
let rf1 = ɵɵembeddedViewStart(1, 2, 0);
|
||||
{
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
ɵɵelement(0, 'div', null, ['foo', '']);
|
||||
viewEl = getNativeByIndex(0, getLView());
|
||||
}
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
},
|
||||
5, 0, [], [],
|
||||
function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵviewQuery(['foo'], true, null);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
let tmp: any;
|
||||
ɵɵqueryRefresh(tmp = ɵɵloadViewQuery<QueryList<any>>()) &&
|
||||
(ctx.query = tmp as QueryList<any>);
|
||||
}
|
||||
});
|
||||
|
||||
const cmptInstance = renderComponent(Cmpt);
|
||||
const qList = (cmptInstance.query as any);
|
||||
expect(qList.length).toBe(2);
|
||||
expect(qList.first.nativeElement).toBe(firstEl);
|
||||
expect(qList.last.nativeElement).toBe(lastEl);
|
||||
|
||||
cmptInstance.exp = true;
|
||||
detectChanges(cmptInstance);
|
||||
expect(qList.length).toBe(3);
|
||||
expect(qList.toArray()[0].nativeElement).toBe(firstEl);
|
||||
expect(qList.toArray()[1].nativeElement).toBe(viewEl);
|
||||
expect(qList.toArray()[2].nativeElement).toBe(lastEl);
|
||||
|
||||
cmptInstance.exp = false;
|
||||
detectChanges(cmptInstance);
|
||||
expect(qList.length).toBe(2);
|
||||
expect(qList.first.nativeElement).toBe(firstEl);
|
||||
expect(qList.last.nativeElement).toBe(lastEl);
|
||||
});
|
||||
|
||||
it('should add results from embedded views in the correct order - views side by side', () => {
|
||||
let firstEl, lastEl;
|
||||
/**
|
||||
* % if (exp1) {
|
||||
* <div #foo></div>
|
||||
* % } if (exp2) {
|
||||
* <span #foo></span>
|
||||
* % }
|
||||
* class Cmpt {
|
||||
* @ViewChildren('foo') query;
|
||||
* }
|
||||
*/
|
||||
const Cmpt = createComponent(
|
||||
'cmpt',
|
||||
function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵcontainer(0);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(0);
|
||||
{
|
||||
if (ctx.exp1) {
|
||||
let rf0 = ɵɵembeddedViewStart(0, 2, 0);
|
||||
{
|
||||
if (rf0 & RenderFlags.Create) {
|
||||
ɵɵelement(0, 'div', null, ['foo', '']);
|
||||
firstEl = getNativeByIndex(0, getLView());
|
||||
}
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
if (ctx.exp2) {
|
||||
let rf1 = ɵɵembeddedViewStart(1, 2, 0);
|
||||
{
|
||||
if (rf1 & RenderFlags.Create) {
|
||||
ɵɵelement(0, 'span', null, ['foo', '']);
|
||||
lastEl = getNativeByIndex(0, getLView());
|
||||
}
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
},
|
||||
1, 0, [], [],
|
||||
function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵviewQuery(['foo'], true, null);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
let tmp: any;
|
||||
ɵɵqueryRefresh(tmp = ɵɵloadViewQuery<QueryList<any>>()) &&
|
||||
(ctx.query = tmp as QueryList<any>);
|
||||
}
|
||||
});
|
||||
|
||||
const cmptInstance = renderComponent(Cmpt);
|
||||
const qList = (cmptInstance.query as any);
|
||||
expect(qList.length).toBe(0);
|
||||
|
||||
cmptInstance.exp2 = true;
|
||||
detectChanges(cmptInstance);
|
||||
expect(qList.length).toBe(1);
|
||||
expect(qList.last.nativeElement).toBe(lastEl);
|
||||
|
||||
cmptInstance.exp1 = true;
|
||||
detectChanges(cmptInstance);
|
||||
expect(qList.length).toBe(2);
|
||||
expect(qList.first.nativeElement).toBe(firstEl);
|
||||
expect(qList.last.nativeElement).toBe(lastEl);
|
||||
});
|
||||
|
||||
it('should add results from embedded views in the correct order - nested views', () => {
|
||||
let firstEl, lastEl;
|
||||
/**
|
||||
* % if (exp1) {
|
||||
* <div #foo></div>
|
||||
* % if (exp2) {
|
||||
* <span #foo></span>
|
||||
* }
|
||||
* % }
|
||||
* class Cmpt {
|
||||
* @ViewChildren('foo') query;
|
||||
* }
|
||||
*/
|
||||
const Cmpt = createComponent(
|
||||
'cmpt',
|
||||
function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵcontainer(0);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(0);
|
||||
{
|
||||
if (ctx.exp1) {
|
||||
let rf0 = ɵɵembeddedViewStart(0, 3, 0);
|
||||
{
|
||||
if (rf0 & RenderFlags.Create) {
|
||||
ɵɵelement(0, 'div', null, ['foo', '']);
|
||||
firstEl = getNativeByIndex(0, getLView());
|
||||
ɵɵcontainer(2);
|
||||
}
|
||||
if (rf0 & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(2);
|
||||
{
|
||||
if (ctx.exp2) {
|
||||
let rf2 = ɵɵembeddedViewStart(0, 2, 0);
|
||||
{
|
||||
if (rf2) {
|
||||
ɵɵelement(0, 'span', null, ['foo', '']);
|
||||
lastEl = getNativeByIndex(0, getLView());
|
||||
}
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
},
|
||||
1, 0, [], [],
|
||||
function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵviewQuery(['foo'], true, null);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
let tmp: any;
|
||||
ɵɵqueryRefresh(tmp = ɵɵloadViewQuery<QueryList<any>>()) &&
|
||||
(ctx.query = tmp as QueryList<any>);
|
||||
}
|
||||
});
|
||||
|
||||
const cmptInstance = renderComponent(Cmpt);
|
||||
const qList = (cmptInstance.query as any);
|
||||
expect(qList.length).toBe(0);
|
||||
|
||||
cmptInstance.exp1 = true;
|
||||
detectChanges(cmptInstance);
|
||||
expect(qList.length).toBe(1);
|
||||
expect(qList.first.nativeElement).toBe(firstEl);
|
||||
|
||||
cmptInstance.exp2 = true;
|
||||
detectChanges(cmptInstance);
|
||||
expect(qList.length).toBe(2);
|
||||
expect(qList.first.nativeElement).toBe(firstEl);
|
||||
expect(qList.last.nativeElement).toBe(lastEl);
|
||||
});
|
||||
|
||||
/**
|
||||
* What is tested here can't be achieved in the Renderer2 as all view queries are deep by
|
||||
* default and can't be marked as shallow by a user.
|
||||
*/
|
||||
it('should support combination of deep and shallow queries', () => {
|
||||
/**
|
||||
* % if (exp) { ">
|
||||
* <div #foo>
|
||||
* <div #foo></div>
|
||||
* </div>
|
||||
* % }
|
||||
* <span #foo></span>
|
||||
* class Cmpt {
|
||||
* @ViewChildren('foo') deep;
|
||||
* @ViewChildren('foo') shallow;
|
||||
* }
|
||||
*/
|
||||
const Cmpt = createComponent(
|
||||
'cmpt',
|
||||
function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵcontainer(0);
|
||||
ɵɵelement(1, 'span', null, ['foo', '']);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
ɵɵcontainerRefreshStart(0);
|
||||
{
|
||||
if (ctx.exp) {
|
||||
let rf0 = ɵɵembeddedViewStart(0, 4, 0);
|
||||
{
|
||||
if (rf0 & RenderFlags.Create) {
|
||||
ɵɵelementStart(0, 'div', null, ['foo', '']);
|
||||
{ ɵɵelement(2, 'div', null, ['foo', '']); }
|
||||
ɵɵelementEnd();
|
||||
}
|
||||
}
|
||||
ɵɵembeddedViewEnd();
|
||||
}
|
||||
}
|
||||
ɵɵcontainerRefreshEnd();
|
||||
}
|
||||
},
|
||||
3, 0, [], [],
|
||||
function(rf: RenderFlags, ctx: any) {
|
||||
if (rf & RenderFlags.Create) {
|
||||
ɵɵviewQuery(['foo'], true, null);
|
||||
ɵɵviewQuery(['foo'], false, null);
|
||||
}
|
||||
if (rf & RenderFlags.Update) {
|
||||
let tmp: any;
|
||||
ɵɵqueryRefresh(tmp = ɵɵloadViewQuery<QueryList<any>>()) &&
|
||||
(ctx.deep = tmp as QueryList<any>);
|
||||
ɵɵqueryRefresh(tmp = ɵɵloadViewQuery<QueryList<any>>()) &&
|
||||
(ctx.shallow = tmp as QueryList<any>);
|
||||
}
|
||||
});
|
||||
|
||||
const cmptInstance = renderComponent(Cmpt);
|
||||
const deep = (cmptInstance.deep as any);
|
||||
const shallow = (cmptInstance.shallow as any);
|
||||
expect(deep.length).toBe(1);
|
||||
expect(shallow.length).toBe(1);
|
||||
|
||||
|
||||
cmptInstance.exp = true;
|
||||
detectChanges(cmptInstance);
|
||||
expect(deep.length).toBe(3);
|
||||
|
||||
// embedded % if blocks should behave the same way as *ngIf, namely they
|
||||
// should match shallow queries on the first level of elements underneath
|
||||
// the embedded view boundary.
|
||||
expect(shallow.length).toBe(2);
|
||||
|
||||
cmptInstance.exp = false;
|
||||
detectChanges(cmptInstance);
|
||||
expect(deep.length).toBe(1);
|
||||
expect(shallow.length).toBe(1);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('queryList', () => {
|
||||
it('should be destroyed when the containing view is destroyed', () => {
|
||||
let queryInstance: QueryList<any>;
|
||||
|
|
|
@ -752,7 +752,7 @@ export declare function ɵɵcontainerRefreshEnd(): void;
|
|||
|
||||
export declare function ɵɵcontainerRefreshStart(index: number): void;
|
||||
|
||||
export declare function ɵɵcontentQuery<T>(directiveIndex: number, predicate: Type<any> | string[], descend: boolean, read: any): QueryList<T>;
|
||||
export declare function ɵɵcontentQuery<T>(directiveIndex: number, predicate: Type<any> | string[], descend: boolean, read: any): void;
|
||||
|
||||
export declare const ɵɵdefaultStyleSanitizer: StyleSanitizeFn;
|
||||
|
||||
|
@ -926,7 +926,7 @@ export declare function ɵɵload<T>(index: number): T;
|
|||
|
||||
export declare function ɵɵloadContentQuery<T>(): QueryList<T>;
|
||||
|
||||
export declare function ɵɵloadViewQuery<T>(): T;
|
||||
export declare function ɵɵloadViewQuery<T>(): QueryList<T>;
|
||||
|
||||
export declare function ɵɵnamespaceHTML(): void;
|
||||
|
||||
|
@ -1115,7 +1115,7 @@ export declare function ɵɵtextInterpolateV(values: any[]): TsickleIssue1009;
|
|||
|
||||
export declare function ɵɵupdateSyntheticHostBinding<T>(propName: string, value: T | NO_CHANGE, sanitizer?: SanitizerFn | null): TsickleIssue1009;
|
||||
|
||||
export declare function ɵɵviewQuery<T>(predicate: Type<any> | string[], descend: boolean, read: any): QueryList<T>;
|
||||
export declare function ɵɵviewQuery<T>(predicate: Type<any> | string[], descend: boolean, read: any): void;
|
||||
|
||||
export declare const PACKAGE_ROOT_URL: InjectionToken<string>;
|
||||
|
||||
|
|
Loading…
Reference in New Issue