diff --git a/packages/core/src/render3/component.ts b/packages/core/src/render3/component.ts index bbe70a08bc..94a2c0382d 100644 --- a/packages/core/src/render3/component.ts +++ b/packages/core/src/render3/component.ts @@ -111,7 +111,7 @@ export function renderComponent( componentDef.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways); rootView[INJECTOR] = opts.injector || null; - const oldView = enterView(rootView, null !); + const oldView = enterView(rootView, null); let elementNode: LElementNode; let component: T; try { @@ -121,7 +121,7 @@ export function renderComponent( elementNode = hostElement(componentTag, hostNode, componentDef, sanitizer); // Create directive instance with factory() and store at index 0 in directives array - component = baseDirectiveCreate(0, componentDef.factory() as T, componentDef); + component = baseDirectiveCreate(0, componentDef.factory() as T, componentDef, elementNode); if (componentDef.hostBindings) { queueHostBindingForCheck(0, componentDef.hostVars); } diff --git a/packages/core/src/render3/component_ref.ts b/packages/core/src/render3/component_ref.ts index 2ccec4d013..7fe8db11ba 100644 --- a/packages/core/src/render3/component_ref.ts +++ b/packages/core/src/render3/component_ref.ts @@ -126,7 +126,7 @@ export class ComponentFactory extends viewEngine_ComponentFactory { rootView[INJECTOR] = ngModule && ngModule.injector || null; // rootView is the parent when bootstrapping - const oldView = enterView(rootView, null !); + const oldView = enterView(rootView, null); let component: T; let elementNode: LElementNode; @@ -137,7 +137,8 @@ export class ComponentFactory extends viewEngine_ComponentFactory { elementNode = hostElement(componentTag, hostNode, this.componentDef); // Create directive instance with factory() and store at index 0 in directives array - component = baseDirectiveCreate(0, this.componentDef.factory(), this.componentDef); + component = + baseDirectiveCreate(0, this.componentDef.factory(), this.componentDef, elementNode); if (this.componentDef.hostBindings) { queueHostBindingForCheck(0, this.componentDef.hostVars); } diff --git a/packages/core/src/render3/di.ts b/packages/core/src/render3/di.ts index 599d6c965b..cf4e7b683d 100644 --- a/packages/core/src/render3/di.ts +++ b/packages/core/src/render3/di.ts @@ -24,7 +24,7 @@ import {Type} from '../type'; import {assertDefined, assertGreaterThan, assertLessThan} from './assert'; import {ComponentFactoryResolver} from './component_ref'; -import {addToViewTree, assertPreviousIsParent, createEmbeddedViewNode, createLContainer, createLNodeObject, createTNode, getPreviousOrParentNode, getRenderer, isComponent, renderEmbeddedTemplate, resolveDirective} from './instructions'; +import {addToViewTree, assertPreviousIsParent, createEmbeddedViewNode, createLContainer, createLNodeObject, createTNode, getPreviousOrParentNode, getPreviousOrParentTNode, getRenderer, isComponent, renderEmbeddedTemplate, resolveDirective} from './instructions'; import {VIEWS} from './interfaces/container'; import {DirectiveDefInternal, RenderFlags} from './interfaces/definition'; import {LInjector} from './interfaces/injector'; @@ -273,10 +273,9 @@ export function injectRenderer2(): Renderer2 { * @experimental */ export function injectAttribute(attrNameToInject: string): string|undefined { - const lNode = getPreviousOrParentNode(); + const tNode = getPreviousOrParentTNode(); ngDevMode && assertNodeOfPossibleTypes( - lNode, TNodeType.Container, TNodeType.Element, TNodeType.ElementContainer); - const tNode = lNode.tNode; + tNode, TNodeType.Container, TNodeType.Element, TNodeType.ElementContainer); ngDevMode && assertDefined(tNode, 'expecting tNode'); const attrs = tNode.attrs; if (attrs) { @@ -573,8 +572,9 @@ export const QUERY_READ_ELEMENT_REF = export const QUERY_READ_FROM_NODE = (new ReadFromInjectorFn((injector: LInjector, node: LNode, directiveIdx: number) => { - ngDevMode && assertNodeOfPossibleTypes( - node, TNodeType.Container, TNodeType.Element, TNodeType.ElementContainer); + ngDevMode && + assertNodeOfPossibleTypes( + node.tNode, TNodeType.Container, TNodeType.Element, TNodeType.ElementContainer); if (directiveIdx > -1) { return node.view[DIRECTIVES] ![directiveIdx]; } @@ -605,17 +605,17 @@ class ElementRef implements viewEngine_ElementRef { export function getOrCreateContainerRef(di: LInjector): viewEngine_ViewContainerRef { if (!di.viewContainerRef) { const vcRefHost = di.node; + const hostTNode = vcRefHost.tNode as TElementNode | TContainerNode; ngDevMode && assertNodeOfPossibleTypes( - vcRefHost, TNodeType.Container, TNodeType.Element, TNodeType.ElementContainer); + hostTNode, TNodeType.Container, TNodeType.Element, TNodeType.ElementContainer); const hostParent = getParentLNode(vcRefHost) !; const lContainer = createLContainer(hostParent, vcRefHost.view, true); const comment = vcRefHost.view[RENDERER].createComment(ngDevMode ? 'container' : ''); - const lContainerNode: LContainerNode = - createLNodeObject(TNodeType.Container, vcRefHost.view, hostParent, comment, lContainer); + const lContainerNode: LContainerNode = createLNodeObject( + TNodeType.Container, vcRefHost.view, vcRefHost.nodeInjector, comment, lContainer); appendChild(hostParent, comment, vcRefHost.view); - const hostTNode = vcRefHost.tNode as TElementNode | TContainerNode; if (!hostTNode.dynamicContainerNode) { hostTNode.dynamicContainerNode = createTNode(TNodeType.Container, -1, null, null, hostTNode, null); @@ -785,9 +785,9 @@ class ViewContainerRef implements viewEngine_ViewContainerRef { */ export function getOrCreateTemplateRef(di: LInjector): viewEngine_TemplateRef { if (!di.templateRef) { - ngDevMode && assertNodeType(di.node, TNodeType.Container); const hostNode = di.node as LContainerNode; const hostTNode = hostNode.tNode; + ngDevMode && assertNodeType(hostTNode, TNodeType.Container); ngDevMode && assertDefined(hostTNode.tViews, 'TView must be allocated'); di.templateRef = new TemplateRef( hostNode.view, getOrCreateElementRef(di), hostTNode.tViews as TView, getRenderer(), @@ -845,4 +845,4 @@ class TemplateRef implements viewEngine_TemplateRef { */ export function templateRefExtractor(lNode: LNodeWithLocalRefs) { return getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(lNode)); -} \ No newline at end of file +} diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index dd878deb1c..fd898f2bd3 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -19,7 +19,7 @@ import {executeHooks, executeInitHooks, queueInitHooks, queueLifecycleHooks} fro import {ACTIVE_INDEX, LContainer, RENDER_PARENT, VIEWS} from './interfaces/container'; import {ComponentDefInternal, ComponentQuery, ComponentTemplate, DirectiveDefInternal, DirectiveDefListOrFactory, InitialStylingFlags, PipeDefListOrFactory, RenderFlags} from './interfaces/definition'; import {LInjector} from './interfaces/injector'; -import {AttributeMarker, InitialInputData, InitialInputs, LContainerNode, LElementContainerNode, LElementNode, LNode, LNodeWithLocalRefs, LProjectionNode, LTextNode, LViewNode, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementNode, TNode, TNodeFlags, TNodeType} from './interfaces/node'; +import {AttributeMarker, InitialInputData, InitialInputs, LContainerNode, LElementContainerNode, LElementNode, LNode, LNodeWithLocalRefs, LProjectionNode, LTextNode, LViewNode, LocalRefExtractor, PropertyAliasValue, PropertyAliases, TAttributes, TContainerNode, TElementNode, TNode, TNodeFlags, TNodeType, TViewNode} from './interfaces/node'; import {CssSelectorList, NG_PROJECT_AS_ATTR_NAME} from './interfaces/projection'; import {LQueries} from './interfaces/query'; import {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, RendererFactory3, RendererStyleFlags3, isProceduralRenderer} from './interfaces/renderer'; @@ -28,7 +28,7 @@ import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert'; import {appendChild, appendProjectedNode, canInsertNativeNode, createTextNode, findComponentHost, getChildLNode, getLViewChild, getNextLNode, getParentLNode, insertView, removeView} from './node_manipulation'; import {isNodeMatchingSelectorList, matchingSelectorIndex} from './node_selector_matcher'; import {StylingContext, allocStylingContext, createStylingContextTemplate, renderStyling as renderElementStyles, updateClassProp as updateElementClassProp, updateStyleProp as updateElementStyleProp, updateStylingMap} from './styling'; -import {assertDataInRangeInternal, isDifferent, loadElementInternal, loadInternal, stringify} from './util'; +import {assertDataInRangeInternal, isDifferent, loadElementInternal, loadInternal, readElementValue, stringify} from './util'; import {ViewRef} from './view_ref'; @@ -125,18 +125,25 @@ export function restoreView(viewToRestore: OpaqueViewState) { contextViewData = viewToRestore as any as LViewData; } -/** Used to set the parent property when nodes are created. */ -let previousOrParentNode: LNode; +/** Used to set the parent property when nodes are created and track query results. */ +let previousOrParentTNode: TNode; export function getPreviousOrParentNode(): LNode { - // top level variables should not be exported for performance reasons (PERF_NOTES.md) - return previousOrParentNode; + return previousOrParentTNode == null || previousOrParentTNode === tView.node ? + viewData[HOST_NODE] : + readElementValue(viewData[previousOrParentTNode.index]); } +export function getPreviousOrParentTNode(): TNode { + // top level variables should not be exported for performance reasons (PERF_NOTES.md) + return previousOrParentTNode; +} + + /** * If `isParent` is: - * - `true`: then `previousOrParentNode` points to a parent node. - * - `false`: then `previousOrParentNode` points to previous node (sibling). + * - `true`: then `previousOrParentTNode` points to a parent node. + * - `false`: then `previousOrParentTNode` points to previous node (sibling). */ let isParent: boolean; @@ -148,18 +155,17 @@ let currentQueries: LQueries|null; * Query instructions can ask for "current queries" in 2 different cases: * - when creating view queries (at the root of a component view, before any node is created - in * this case currentQueries points to view queries) - * - when creating content queries (inb this previousOrParentNode points to a node on which we + * - when creating content queries (i.e. this previousOrParentTNode points to a node on which we * create content queries). */ export function getOrCreateCurrentQueries( QueryType: {new (parent: null, shallow: null, deep: null): LQueries}): LQueries { - const tNode = previousOrParentNode.tNode; - // if this is the first content query on a node, any existing LQueries needs to be cloned // in subsequent template passes, the cloning occurs before directive instantiation. - if (previousOrParentNode.data !== viewData && !isContentQueryHost(tNode)) { + if (previousOrParentTNode && previousOrParentTNode !== tView.node && + !isContentQueryHost(previousOrParentTNode)) { currentQueries && (currentQueries = currentQueries.clone()); - tNode.flags |= TNodeFlags.hasContentQuery; + previousOrParentTNode.flags |= TNodeFlags.hasContentQuery; } return currentQueries || (currentQueries = new QueryType(null, null, null)); @@ -256,7 +262,8 @@ const enum BindingDirection { * @param host Element to which the View is a child of * @returns the previous state; */ -export function enterView(newView: LViewData, host: LElementNode | LViewNode | null): LViewData { +export function enterView( + newView: LViewData, hostTNode: TElementNode | TViewNode | null): LViewData { const oldView: LViewData = viewData; directives = newView && newView[DIRECTIVES]; tView = newView && newView[TVIEW]; @@ -266,10 +273,8 @@ export function enterView(newView: LViewData, host: LElementNode | LViewNode | n bindingRootIndex = newView && tView.bindingStartIndex; renderer = newView && newView[RENDERER]; - if (host != null) { - previousOrParentNode = host; - isParent = true; - } + previousOrParentTNode = hostTNode !; + isParent = true; viewData = contextViewData = newView; oldView && (oldView[QUERIES] = currentQueries); @@ -387,13 +392,13 @@ export function createLViewData( * (same properties assigned in the same order). */ export function createLNodeObject( - type: TNodeType, currentView: LViewData, parent: LNode | null, + type: TNodeType, currentView: LViewData, nodeInjector: LInjector | null, native: RText | RElement | RComment | null, state: any): LElementNode<extNode&LViewNode&LContainerNode&LProjectionNode { return { native: native as any, view: currentView, - nodeInjector: parent ? parent.nodeInjector : null, + nodeInjector: nodeInjector, data: state, tNode: null !, dynamicLContainerNode: null @@ -431,15 +436,16 @@ export function createLNode( index: number, type: TNodeType, native: RText | RElement | RComment | null, name: string | null, attrs: TAttributes | null, state?: null | LViewData | LContainer): LElementNode<extNode& LViewNode&LContainerNode&LProjectionNode { - const parent = isParent ? previousOrParentNode : - previousOrParentNode && getParentLNode(previousOrParentNode) !as LNode; + const parent = + isParent ? previousOrParentTNode : previousOrParentTNode && previousOrParentTNode.parent; + // Parents cannot cross component boundaries because components will be used in multiple places, // so it's only set if the view is the same. - const tParent = - parent && parent.view === viewData ? parent.tNode as TElementNode | TContainerNode : null; + const parentInSameView = parent && tView && parent !== tView.node; + const tParent = parentInSameView ? parent as TElementNode | TContainerNode : null; const isState = state != null; - const node = createLNodeObject(type, viewData, parent, native, isState ? state as any : null); + const node = createLNodeObject(type, viewData, null, native, isState ? state as any : null); if (index === -1 || type === TNodeType.View) { // View nodes are not stored in data because they can be added / removed at runtime (which @@ -459,10 +465,10 @@ export function createLNode( if (tData[adjustedIndex] == null) { const tNode = tData[adjustedIndex] = createTNode(type, adjustedIndex, name, attrs, tParent, null); - if (!isParent && previousOrParentNode) { - const previousTNode = previousOrParentNode.tNode; - previousTNode.next = tNode; - if (previousTNode.dynamicContainerNode) previousTNode.dynamicContainerNode.next = tNode; + if (!isParent && previousOrParentTNode) { + previousOrParentTNode.next = tNode; + if (previousOrParentTNode.dynamicContainerNode) + previousOrParentTNode.dynamicContainerNode.next = tNode; } } @@ -472,26 +478,31 @@ export function createLNode( } // Now link ourselves into the tree. - if (isParent) { - if (previousOrParentNode.tNode.child == null && previousOrParentNode.view === viewData || - previousOrParentNode.tNode.type === TNodeType.View) { + if (isParent && previousOrParentTNode) { + if (previousOrParentTNode.child == null && parentInSameView || + previousOrParentTNode.type === TNodeType.View) { // We are in the same view, which means we are adding content node to the parent View. - previousOrParentNode.tNode.child = node.tNode; + previousOrParentTNode.child = node.tNode; } } } + // TODO: temporary, remove when removing LNode.nodeInjector + const parentLNode = index === -1 ? null : getParentLNode(node); + if (parentLNode) node.nodeInjector = parentLNode.nodeInjector; - // View nodes and host elements need to set their host node (components set host nodes later) + // View nodes and host elements need to set their host node (components do not save host TNodes) if ((type & TNodeType.ViewOrElement) === TNodeType.ViewOrElement && isState) { const lViewData = state as LViewData; ngDevMode && assertEqual( lViewData[HOST_NODE], null, 'lViewData[HOST_NODE] should not have been initialized'); lViewData[HOST_NODE] = node; - if (firstTemplatePass) lViewData[TVIEW].node = node.tNode; + if (lViewData[TVIEW].firstTemplatePass) { + lViewData[TVIEW].node = node.tNode as TViewNode | TElementNode; + } } - previousOrParentNode = node; + previousOrParentTNode = node.tNode; isParent = true; return node; } @@ -520,7 +531,7 @@ export function adjustBlueprintForNewNode(view: LViewData) { */ export function resetComponentState() { isParent = false; - previousOrParentNode = null !; + previousOrParentTNode = null !; elementDepthCount = 0; } @@ -553,7 +564,7 @@ export function renderTemplate( } const hostView = host.data !; ngDevMode && assertDefined(hostView, 'Host node should have an LView defined in host.data.'); - renderComponentOrTemplate(host, hostView, context, templateFn); + renderComponentOrTemplate(hostView, context, templateFn); return host; } @@ -566,9 +577,9 @@ export function createEmbeddedViewNode( tView: TView, context: T, declarationView: LViewData, renderer: Renderer3, queries?: LQueries | null): LViewNode { const _isParent = isParent; - const _previousOrParentNode = previousOrParentNode; + const _previousOrParentTNode = previousOrParentTNode; isParent = true; - previousOrParentNode = null !; + previousOrParentTNode = null !; const lView = createLViewData(renderer, tView, context, LViewFlags.CheckAlways, getCurrentSanitizer()); @@ -580,7 +591,7 @@ export function createEmbeddedViewNode( const viewNode = createLNode(-1, TNodeType.View, null, null, null, lView); isParent = _isParent; - previousOrParentNode = _previousOrParentNode; + previousOrParentTNode = _previousOrParentTNode; return viewNode; } @@ -598,7 +609,7 @@ export function renderEmbeddedTemplate( viewNode: LViewNode | LElementNode, tView: TView, context: T, rf: RenderFlags): LViewNode| LElementNode { const _isParent = isParent; - const _previousOrParentNode = previousOrParentNode; + const _previousOrParentTNode = previousOrParentTNode; let oldView: LViewData; if (viewNode.data ![PARENT] == null && viewNode.data ![CONTEXT] && !tView.template) { // This is a root view inside the view tree @@ -606,9 +617,9 @@ export function renderEmbeddedTemplate( } else { try { isParent = true; - previousOrParentNode = null !; + previousOrParentTNode = null !; - oldView = enterView(viewNode.data !, viewNode); + oldView = enterView(viewNode.data !, tView.node); namespaceHTML(); tView.template !(rf, context); if (rf & RenderFlags.Update) { @@ -622,7 +633,7 @@ export function renderEmbeddedTemplate( const isCreationOnly = (rf & RenderFlags.Create) === RenderFlags.Create; leaveView(oldView !, isCreationOnly); isParent = _isParent; - previousOrParentNode = _previousOrParentNode; + previousOrParentTNode = _previousOrParentTNode; } } return viewNode; @@ -644,9 +655,8 @@ export function nextContext(level: number = 1): T { } export function renderComponentOrTemplate( - node: LElementNode, hostView: LViewData, componentOrContext: T, - templateFn?: ComponentTemplate) { - const oldView = enterView(hostView, node); + hostView: LViewData, componentOrContext: T, templateFn?: ComponentTemplate) { + const oldView = enterView(hostView, null); try { if (rendererFactory.begin) { rendererFactory.begin(); @@ -757,14 +767,15 @@ export function elementContainerEnd(): void { isParent = false; } else { ngDevMode && assertHasParent(); - previousOrParentNode = getParentLNode(previousOrParentNode) as LElementContainerNode; + previousOrParentTNode = previousOrParentTNode.parent !; } - ngDevMode && assertNodeType(previousOrParentNode, TNodeType.ElementContainer); + // Can be read directly because ng-containers can't have style bindings + const previousNode = viewData[previousOrParentTNode.index]; + ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.ElementContainer); + currentQueries && (currentQueries = currentQueries.addNode(previousNode)); - currentQueries && (currentQueries = currentQueries.addNode(previousOrParentNode)); - - queueLifecycleHooks(previousOrParentNode.tNode.flags, tView); + queueLifecycleHooks(previousOrParentTNode.flags, tView); } /** @@ -847,7 +858,7 @@ function createDirectivesAndLocals( localRefExtractor: LocalRefExtractor = nativeNodeLocalRefExtractor) { if (firstTemplatePass) { ngDevMode && ngDevMode.firstTemplatePass++; - cacheMatchingDirectivesForNode(lNode.tNode, tView, localRefs || null); + cacheMatchingDirectivesForNode(previousOrParentTNode, tView, localRefs || null); } else { instantiateDirectivesDirectly(); } @@ -913,7 +924,7 @@ export function resolveDirective( /** Stores index of component's host element so it will be queued for view refresh during CD. */ function queueComponentIndexForCheck(): void { if (firstTemplatePass) { - (tView.components || (tView.components = [])).push(previousOrParentNode.tNode.index); + (tView.components || (tView.components = [])).push(previousOrParentTNode.index); } } @@ -929,7 +940,7 @@ export function queueHostBindingForCheck(dirIndex: number, hostVars: number): vo viewData.push(NO_CHANGE); } (tView.hostBindings || (tView.hostBindings = [ - ])).push(dirIndex, previousOrParentNode.tNode.index - HEADER_OFFSET); + ])).push(dirIndex, previousOrParentTNode.index - HEADER_OFFSET); } /** Sets the context for a ChangeDetectorRef to the given instance. */ @@ -955,15 +966,14 @@ function instantiateDirectivesDirectly() { ngDevMode && assertEqual( firstTemplatePass, false, `Directives should only be instantiated directly after first template pass`); - const tNode = previousOrParentNode.tNode; - const count = tNode.flags & TNodeFlags.DirectiveCountMask; + const count = previousOrParentTNode.flags & TNodeFlags.DirectiveCountMask; - if (isContentQueryHost(tNode) && currentQueries) { + if (isContentQueryHost(previousOrParentTNode) && currentQueries) { currentQueries = currentQueries.clone(); } if (count > 0) { - const start = tNode.flags >> TNodeFlags.DirectiveStartingIndexShift; + const start = previousOrParentTNode.flags >> TNodeFlags.DirectiveStartingIndexShift; const end = start + count; const tDirectives = tView.directives !; @@ -1213,40 +1223,38 @@ export function hostElement( */ export function listener( eventName: string, listenerFn: (e?: any) => any, useCapture = false): void { - ngDevMode && - assertNodeOfPossibleTypes( - previousOrParentNode, TNodeType.Element, TNodeType.Container, TNodeType.ElementContainer); - const node = previousOrParentNode; + const tNode = previousOrParentTNode; + ngDevMode && assertNodeOfPossibleTypes( + tNode, TNodeType.Element, TNodeType.Container, TNodeType.ElementContainer); // add native event listener - applicable to elements only - if (previousOrParentNode.tNode.type === TNodeType.Element) { - const native = node.native as RElement; + if (tNode.type === TNodeType.Element) { + const node = getPreviousOrParentNode() as LElementNode; ngDevMode && ngDevMode.rendererAddEventListener++; // In order to match current behavior, native DOM event listeners must be added for all // events (including outputs). if (isProceduralRenderer(renderer)) { const wrappedListener = wrapListenerWithDirtyLogic(viewData, listenerFn); - const cleanupFn = renderer.listen(native, eventName, wrappedListener); + const cleanupFn = renderer.listen(node.native, eventName, wrappedListener); storeCleanupFn(viewData, cleanupFn); } else { const wrappedListener = wrapListenerWithDirtyAndDefault(viewData, listenerFn); - native.addEventListener(eventName, wrappedListener, useCapture); + node.native.addEventListener(eventName, wrappedListener, useCapture); const cleanupInstances = getCleanup(viewData); cleanupInstances.push(wrappedListener); if (firstTemplatePass) { getTViewCleanup(viewData).push( - eventName, node.tNode.index, cleanupInstances !.length - 1, useCapture); + eventName, tNode.index, cleanupInstances !.length - 1, useCapture); } } } // subscribe to directive outputs - let tNode: TNode|null = node.tNode; if (tNode.outputs === undefined) { // if we create TNode here, inputs must be undefined so we know they still need to be // checked - tNode.outputs = generatePropertyAliases(node.tNode.flags, BindingDirection.Output); + tNode.outputs = generatePropertyAliases(tNode.flags, BindingDirection.Output); } const outputs = tNode.outputs; @@ -1307,11 +1315,12 @@ export function elementEnd(): void { isParent = false; } else { ngDevMode && assertHasParent(); - previousOrParentNode = getParentLNode(previousOrParentNode) as LElementNode; + previousOrParentTNode = previousOrParentTNode.parent !; } - ngDevMode && assertNodeType(previousOrParentNode, TNodeType.Element); - currentQueries && (currentQueries = currentQueries.addNode(previousOrParentNode)); - queueLifecycleHooks(previousOrParentNode.tNode.flags, tView); + ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.Element); + currentQueries && (currentQueries = currentQueries.addNode(getPreviousOrParentNode())); + + queueLifecycleHooks(previousOrParentTNode.flags, tView); elementDepthCount--; } @@ -1514,7 +1523,7 @@ export function elementStyling( classDeclarations?: (string | boolean | InitialStylingFlags)[] | null, styleDeclarations?: (string | boolean | InitialStylingFlags)[] | null, styleSanitizer?: StyleSanitizeFn | null): void { - const tNode = previousOrParentNode.tNode; + const tNode = previousOrParentTNode; if (!tNode.stylingTemplate) { // initialize the styling template. tNode.stylingTemplate = @@ -1693,14 +1702,13 @@ export function textBinding(index: number, value: T | NO_CHANGE): void { export function directiveCreate( directiveDefIdx: number, directive: T, directiveDef: DirectiveDefInternal| ComponentDefInternal): T { - const instance = baseDirectiveCreate(directiveDefIdx, directive, directiveDef); - - ngDevMode && assertDefined(previousOrParentNode.tNode, 'previousOrParentNode.tNode'); - const tNode = previousOrParentNode.tNode; + const hostNode = getPreviousOrParentNode(); + const instance = baseDirectiveCreate(directiveDefIdx, directive, directiveDef, hostNode); const isComponent = (directiveDef as ComponentDefInternal).template; if (isComponent) { - addComponentLogic(directiveDefIdx, directive, directiveDef as ComponentDefInternal); + addComponentLogic( + directiveDefIdx, directive, directiveDef as ComponentDefInternal, hostNode); } if (firstTemplatePass) { @@ -1711,8 +1719,9 @@ export function directiveCreate( if (directiveDef.hostBindings) queueHostBindingForCheck(directiveDefIdx, directiveDef.hostVars); } - if (tNode && tNode.attrs) { - setInputsFromAttrs(directiveDefIdx, instance, directiveDef.inputs, tNode); + ngDevMode && assertDefined(previousOrParentTNode, 'previousOrParentTNode'); + if (previousOrParentTNode && previousOrParentTNode.attrs) { + setInputsFromAttrs(directiveDefIdx, instance, directiveDef.inputs, previousOrParentTNode); } if (directiveDef.contentQueries) { @@ -1723,24 +1732,24 @@ export function directiveCreate( } function addComponentLogic( - directiveIndex: number, instance: T, def: ComponentDefInternal): void { + directiveIndex: number, instance: T, def: ComponentDefInternal, hostNode: LNode): void { const tView = getOrCreateTView( def.template, def.consts, def.vars, def.directiveDefs, def.pipeDefs, def.viewQuery); // Only component views should be added to the view tree directly. Embedded views are // accessed through their containers because they may be removed / re-added later. const componentView = addToViewTree( - viewData, previousOrParentNode.tNode.index as number, + viewData, previousOrParentTNode.index as number, createLViewData( - rendererFactory.createRenderer(previousOrParentNode.native as RElement, def), tView, - instance, def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, getCurrentSanitizer())); + rendererFactory.createRenderer(hostNode.native as RElement, def), tView, instance, + def.onPush ? LViewFlags.Dirty : LViewFlags.CheckAlways, getCurrentSanitizer())); // We need to set the host node/data here because when the component LNode was created, // we didn't yet know it was a component (just an element). - (previousOrParentNode as{data: LViewData}).data = componentView; - (componentView as LViewData)[HOST_NODE] = previousOrParentNode as LElementNode; + (hostNode as{data: LViewData}).data = componentView; + (componentView as LViewData)[HOST_NODE] = hostNode as LElementNode; - initChangeDetectorIfExisting(previousOrParentNode.nodeInjector, instance, componentView); + initChangeDetectorIfExisting(hostNode.nodeInjector, instance, componentView); if (firstTemplatePass) queueComponentIndexForCheck(); } @@ -1752,8 +1761,8 @@ function addComponentLogic( * current Angular. Example: local refs and inputs on root component. */ export function baseDirectiveCreate( - index: number, directive: T, - directiveDef: DirectiveDefInternal| ComponentDefInternal): T { + index: number, directive: T, directiveDef: DirectiveDefInternal| ComponentDefInternal, + hostNode: LNode): T { ngDevMode && assertEqual( viewData[BINDING_INDEX], tView.bindingStartIndex, 'directives should be created before any bindings'); @@ -1767,28 +1776,27 @@ export function baseDirectiveCreate( directives[index] = directive; if (firstTemplatePass) { - const flags = previousOrParentNode.tNode.flags; + const flags = previousOrParentTNode.flags; if ((flags & TNodeFlags.DirectiveCountMask) === 0) { // When the first directive is created: // - save the index, // - set the number of directives to 1 - previousOrParentNode.tNode.flags = + previousOrParentTNode.flags = index << TNodeFlags.DirectiveStartingIndexShift | flags & TNodeFlags.isComponent | 1; } else { // Only need to bump the size when subsequent directives are created ngDevMode && assertNotEqual( flags & TNodeFlags.DirectiveCountMask, TNodeFlags.DirectiveCountMask, 'Reached the max number of directives'); - previousOrParentNode.tNode.flags++; + previousOrParentTNode.flags++; } } else { const diPublic = directiveDef !.diPublic; if (diPublic) diPublic(directiveDef !); } - if (directiveDef !.attributes != null && previousOrParentNode.tNode.type == TNodeType.Element) { - setUpAttributes( - (previousOrParentNode as LElementNode).native, directiveDef !.attributes as string[]); + if (directiveDef !.attributes != null && previousOrParentTNode.type == TNodeType.Element) { + setUpAttributes((hostNode as LElementNode).native, directiveDef !.attributes as string[]); } return directive; @@ -1949,7 +1957,8 @@ function containerInternal( viewData[BINDING_INDEX], tView.bindingStartIndex, 'container nodes should be created before any bindings'); - const currentParent = isParent ? previousOrParentNode : getParentLNode(previousOrParentNode) !; + const previousNode: LNode = getPreviousOrParentNode(); + const currentParent = isParent ? previousNode : getParentLNode(previousNode) !; const lContainer = createLContainer(currentParent, viewData); ngDevMode && ngDevMode.rendererCreateComment++; @@ -1966,7 +1975,7 @@ function containerInternal( lContainer[QUERIES] = currentQueries.container(); } - ngDevMode && assertNodeType(previousOrParentNode, TNodeType.Container); + ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.Container); return node; } @@ -1976,10 +1985,12 @@ function containerInternal( * @param index The index of the container in the data array */ export function containerRefreshStart(index: number): void { - previousOrParentNode = loadElement(index) as LNode; - ngDevMode && assertNodeType(previousOrParentNode, TNodeType.Container); + previousOrParentTNode = loadInternal(index, tView.data) as TNode; + + ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.Container); isParent = true; - (previousOrParentNode as LContainerNode).data[ACTIVE_INDEX] = 0; + // Inline containers cannot have style bindings, so we can read the value directly + (viewData[previousOrParentTNode.index] as LContainerNode).data[ACTIVE_INDEX] = 0; if (!checkNoChangesMode) { // We need to execute init hooks here so ngOnInit hooks are called in top level views @@ -1997,13 +2008,14 @@ export function containerRefreshEnd(): void { if (isParent) { isParent = false; } else { - ngDevMode && assertNodeType(previousOrParentNode, TNodeType.View); + ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.View); ngDevMode && assertHasParent(); - previousOrParentNode = getParentLNode(previousOrParentNode) !; + previousOrParentTNode = previousOrParentTNode.parent !; } - ngDevMode && assertNodeType(previousOrParentNode, TNodeType.Container); - const container = previousOrParentNode as LContainerNode; - ngDevMode && assertNodeType(container, TNodeType.Container); + + // Inline containers cannot have style bindings, so we can read the value directly + const container = viewData[previousOrParentTNode.index]; + ngDevMode && assertNodeType(previousOrParentTNode, TNodeType.Container); const nextIndex = container.data[ACTIVE_INDEX] !; // remove extra views at the end of the container @@ -2072,29 +2084,34 @@ function scanForView( * @return boolean Whether or not this view is in creation mode */ export function embeddedViewStart(viewBlockId: number, consts: number, vars: number): RenderFlags { - const container = - (isParent ? previousOrParentNode : getParentLNode(previousOrParentNode)) as LContainerNode; - ngDevMode && assertNodeType(container, TNodeType.Container); + // The previous node can be a view node if we are processing an inline for loop + const containerTNode = previousOrParentTNode.type === TNodeType.View ? + previousOrParentTNode.parent ! : + previousOrParentTNode; + // Inline containers cannot have style bindings, so we can read the value directly + const container = viewData[containerTNode.index] as LContainerNode; + + ngDevMode && assertNodeType(containerTNode, TNodeType.Container); const lContainer = container.data; let viewNode: LViewNode|null = scanForView(container, lContainer[ACTIVE_INDEX] !, viewBlockId); if (viewNode) { - previousOrParentNode = viewNode; - ngDevMode && assertNodeType(previousOrParentNode, TNodeType.View); + const embeddedView = viewNode.data; isParent = true; - enterView(viewNode.data, viewNode); + enterView(embeddedView, embeddedView[TVIEW].node); } else { // When we create a new LView, we always reset the state of the instructions. const newView = createLViewData( - renderer, getOrCreateEmbeddedTView(viewBlockId, consts, vars, container), null, + renderer, + getOrCreateEmbeddedTView(viewBlockId, consts, vars, containerTNode as TContainerNode), null, LViewFlags.CheckAlways, getCurrentSanitizer()); if (lContainer[QUERIES]) { newView[QUERIES] = lContainer[QUERIES] !.createView(); } - enterView( - newView, viewNode = createLNode(viewBlockId, TNodeType.View, null, null, null, newView)); + viewNode = createLNode(viewBlockId, TNodeType.View, null, null, null, newView); + enterView(newView, newView[TVIEW].node); } if (container) { if (creationMode) { @@ -2116,13 +2133,13 @@ export function embeddedViewStart(viewBlockId: number, consts: number, vars: num * @param viewIndex The index of the TView in TNode.tViews * @param consts The number of nodes, local refs, and pipes in this template * @param vars The number of bindings and pure function bindings in this template - * @param parent The parent container in which to look for the view's static data + * @param container The parent container in which to look for the view's static data * @returns TView */ function getOrCreateEmbeddedTView( - viewIndex: number, consts: number, vars: number, parent: LContainerNode): TView { + viewIndex: number, consts: number, vars: number, parent: TContainerNode): TView { ngDevMode && assertNodeType(parent, TNodeType.Container); - const containerTViews = (parent !.tNode as TContainerNode).tViews as TView[]; + const containerTViews = parent.tViews as TView[]; ngDevMode && assertDefined(containerTViews, 'TView expected'); ngDevMode && assertEqual(Array.isArray(containerTViews), true, 'TViews should be in an array'); if (viewIndex >= containerTViews.length || containerTViews[viewIndex] == null) { @@ -2134,12 +2151,11 @@ function getOrCreateEmbeddedTView( /** Marks the end of an embedded view. */ export function embeddedViewEnd(): void { + const viewHost = tView.node; refreshDescendantViews(); - isParent = false; - previousOrParentNode = viewData[HOST_NODE] as LViewNode; leaveView(viewData[PARENT] !); - ngDevMode && assertEqual(isParent, false, 'isParent'); - ngDevMode && assertNodeType(previousOrParentNode, TNodeType.View); + previousOrParentTNode = viewHost !; + isParent = false; } ///////////// @@ -2151,8 +2167,8 @@ export function embeddedViewEnd(): void { */ export function componentRefresh(adjustedElementIndex: number): void { ngDevMode && assertDataInRange(adjustedElementIndex); - const element = viewData[adjustedElementIndex] as LElementNode; - ngDevMode && assertNodeType(element, TNodeType.Element); + const element = readElementValue(viewData[adjustedElementIndex]) as LElementNode; + ngDevMode && assertNodeType(tView.data[adjustedElementIndex] as TNode, TNodeType.Element); ngDevMode && assertDefined(element.data, `Component's host node should have an LViewData attached.`); const hostView = element.data !; @@ -2414,10 +2430,7 @@ export function tick(component: T): void { function tickRootContext(rootContext: RootContext) { for (let i = 0; i < rootContext.components.length; i++) { const rootComponent = rootContext.components[i]; - const hostNode = _getComponentHostLElementNode(rootComponent, true); - - ngDevMode && assertDefined(hostNode.data, 'Component host node should be attached to an LView'); - renderComponentOrTemplate(hostNode, getRootView(rootComponent), rootComponent); + renderComponentOrTemplate(getRootView(rootComponent), rootComponent); } } @@ -2505,8 +2518,8 @@ export function checkNoChangesInRootView(lViewData: LViewData): void { /** Checks the view of the component provided. Does not gate on dirty checks or execute doCheck. */ export function detectChangesInternal( hostView: LViewData, hostNode: LElementNode, component: T) { - const oldView = enterView(hostView, hostNode); const hostTView = hostView[TVIEW]; + const oldView = enterView(hostView, null); const templateFn = hostTView.template !; const viewQuery = hostTView.viewQuery; @@ -2845,11 +2858,11 @@ export function registerContentQuery(queryList: QueryList): void { } export function assertPreviousIsParent() { - assertEqual(isParent, true, 'previousOrParentNode should be a parent'); + assertEqual(isParent, true, 'previousOrParentTNode should be a parent'); } function assertHasParent() { - assertDefined(getParentLNode(previousOrParentNode), 'previousOrParentNode should have a parent'); + assertDefined(previousOrParentTNode.parent, 'previousOrParentTNode should have a parent'); } function assertDataInRange(index: number, arr?: any[]) { diff --git a/packages/core/src/render3/interfaces/view.ts b/packages/core/src/render3/interfaces/view.ts index bf4edfc8f2..4855f2aaa7 100644 --- a/packages/core/src/render3/interfaces/view.ts +++ b/packages/core/src/render3/interfaces/view.ts @@ -12,7 +12,7 @@ import {Sanitizer} from '../../sanitization/security'; import {LContainer} from './container'; import {ComponentQuery, ComponentTemplate, DirectiveDefInternal, DirectiveDefList, PipeDefInternal, PipeDefList} from './definition'; -import {LElementNode, LViewNode, TNode} from './node'; +import {LElementNode, LViewNode, TElementNode, TNode, TViewNode} from './node'; import {LQueries} from './query'; import {Renderer3} from './renderer'; @@ -277,9 +277,15 @@ export interface TView { * We need this pointer to be able to efficiently find this node when inserting the view * into an anchor. * - * If this is a `TNode` for an `LElementNode`, this is the TView of a component. + * If this is a `TElementNode`, this is the view of a root component. It has exactly one + * root TNode. + * + * If this is null, this is the view of a component that is not at root. We do not store + * the host TNodes for child component views because they can potentially have several + * different host TNodes, depending on where the component is being used. These host + * TNodes cannot be shared (due to different indices, etc). */ - node: TNode; + node: TViewNode|TElementNode|null; /** Whether or not this template has been processed. */ firstTemplatePass: boolean; diff --git a/packages/core/src/render3/node_assert.ts b/packages/core/src/render3/node_assert.ts index a4e1d991df..647725ac6a 100644 --- a/packages/core/src/render3/node_assert.ts +++ b/packages/core/src/render3/node_assert.ts @@ -7,19 +7,19 @@ */ import {assertDefined, assertEqual} from './assert'; -import {LNode, TNodeType} from './interfaces/node'; +import {LNode, TNode, TNodeType} from './interfaces/node'; -export function assertNodeType(node: LNode, type: TNodeType) { - assertDefined(node, 'should be called with a node'); - assertEqual(node.tNode.type, type, `should be a ${typeName(type)}`); +export function assertNodeType(tNode: TNode, type: TNodeType) { + assertDefined(tNode, 'should be called with a TNode'); + assertEqual(tNode.type, type, `should be a ${typeName(type)}`); } -export function assertNodeOfPossibleTypes(node: LNode, ...types: TNodeType[]) { - assertDefined(node, 'should be called with a node'); - const found = types.some(type => node.tNode.type === type); +export function assertNodeOfPossibleTypes(tNode: TNode, ...types: TNodeType[]) { + assertDefined(tNode, 'should be called with a TNode'); + const found = types.some(type => tNode.type === type); assertEqual( found, true, - `Should be one of ${types.map(typeName).join(', ')} but got ${typeName(node.tNode.type)}`); + `Should be one of ${types.map(typeName).join(', ')} but got ${typeName(tNode.type)}`); } function typeName(type: TNodeType): string { diff --git a/packages/core/src/render3/node_manipulation.ts b/packages/core/src/render3/node_manipulation.ts index e09dcbf77c..b1c051d9da 100644 --- a/packages/core/src/render3/node_manipulation.ts +++ b/packages/core/src/render3/node_manipulation.ts @@ -13,7 +13,7 @@ import {LContainer, RENDER_PARENT, VIEWS, unusedValueExportToPlacateAjd as unuse import {LContainerNode, LElementContainerNode, LElementNode, LNode, LProjectionNode, LTextNode, LViewNode, TNode, TNodeFlags, TNodeType, unusedValueExportToPlacateAjd as unused2} from './interfaces/node'; import {unusedValueExportToPlacateAjd as unused3} from './interfaces/projection'; import {ProceduralRenderer3, RComment, RElement, RNode, RText, Renderer3, isProceduralRenderer, unusedValueExportToPlacateAjd as unused4} from './interfaces/renderer'; -import {CLEANUP, CONTAINER_INDEX, DIRECTIVES, FLAGS, HEADER_OFFSET, HOST_NODE, HookData, LViewData, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, TVIEW, unusedValueExportToPlacateAjd as unused5} from './interfaces/view'; +import {CLEANUP, CONTAINER_INDEX, DECLARATION_VIEW, DIRECTIVES, FLAGS, HEADER_OFFSET, HOST_NODE, HookData, LViewData, LViewFlags, NEXT, PARENT, QUERIES, RENDERER, TVIEW, unusedValueExportToPlacateAjd as unused5} from './interfaces/view'; import {assertNodeOfPossibleTypes, assertNodeType} from './node_assert'; import {readElementValue, stringify} from './util'; @@ -194,7 +194,7 @@ export function findComponentHost(lViewData: LViewData): LElementNode { viewRootLNode = lViewData[HOST_NODE]; } - ngDevMode && assertNodeType(viewRootLNode, TNodeType.Element); + ngDevMode && assertNodeType(viewRootLNode.tNode, TNodeType.Element); ngDevMode && assertDefined(viewRootLNode.data, 'node.data'); return viewRootLNode as LElementNode; @@ -246,8 +246,8 @@ export function addRemoveViewFromContainer( export function addRemoveViewFromContainer( container: LContainerNode, rootNode: LViewNode, insertMode: boolean, beforeNode?: RNode | null): void { - ngDevMode && assertNodeType(container, TNodeType.Container); - ngDevMode && assertNodeType(rootNode, TNodeType.View); + ngDevMode && assertNodeType(container.tNode, TNodeType.Container); + ngDevMode && assertNodeType(rootNode.tNode, TNodeType.View); const parentNode = container.data[RENDER_PARENT]; const parent = parentNode ? parentNode.native : null; if (parent) { @@ -544,7 +544,7 @@ function canInsertNativeChildOfElement(parent: LElementNode, currentView: LViewD * the container itself has it render parent determined. */ function canInsertNativeChildOfView(parent: LViewNode): boolean { - ngDevMode && assertNodeType(parent, TNodeType.View); + ngDevMode && assertNodeType(parent.tNode, TNodeType.View); // Because we are inserting into a `View` the `View` may be disconnected. const grandParentContainer = getParentLNode(parent) as LContainerNode; @@ -552,7 +552,7 @@ function canInsertNativeChildOfView(parent: LViewNode): boolean { // The `View` is not inserted into a `Container` we have to delay insertion. return false; } - ngDevMode && assertNodeType(grandParentContainer, TNodeType.Container); + ngDevMode && assertNodeType(grandParentContainer.tNode, TNodeType.Container); if (grandParentContainer.data[RENDER_PARENT] == null) { // The parent `Container` itself is disconnected. So we have to delay. return false; @@ -584,7 +584,7 @@ function canInsertNativeChildOfView(parent: LViewNode): boolean { export function canInsertNativeNode(parent: LNode, currentView: LViewData): boolean { // We can only insert into a Component or View. Any other type should be an Error. ngDevMode && assertNodeOfPossibleTypes( - parent, TNodeType.Element, TNodeType.ElementContainer, TNodeType.View); + parent.tNode, TNodeType.Element, TNodeType.ElementContainer, TNodeType.View); if (parent.tNode.type === TNodeType.Element) { // Parent is a regular element or a component diff --git a/packages/core/src/render3/util.ts b/packages/core/src/render3/util.ts index 338ccf2d20..adb5fae2da 100644 --- a/packages/core/src/render3/util.ts +++ b/packages/core/src/render3/util.ts @@ -8,7 +8,7 @@ import {devModeEqual} from '../change_detection/change_detection_util'; import {assertLessThan} from './assert'; import {LElementNode} from './interfaces/node'; -import {HEADER_OFFSET, LViewData} from './interfaces/view'; +import {HEADER_OFFSET, LViewData, TData} from './interfaces/view'; /** * Returns wether the values are different from a change detection stand point. @@ -66,8 +66,8 @@ export function flatten(list: any[]): any[] { return result; } -/** Retrieves a value from any `LViewData`. */ -export function loadInternal(index: number, arr: LViewData): T { +/** Retrieves a value from any `LViewData` or `TData`. */ +export function loadInternal(index: number, arr: LViewData | TData): T { ngDevMode && assertDataInRangeInternal(index + HEADER_OFFSET, arr); return arr[index + HEADER_OFFSET]; } diff --git a/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json b/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json index 702fa9f978..27e5bdcde0 100644 --- a/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo_r2/bundle.golden_symbols.json @@ -1721,6 +1721,9 @@ { "name": "getPreviousOrParentNode" }, + { + "name": "getPreviousOrParentTNode" + }, { "name": "getPromiseCtor" }, diff --git a/packages/core/test/render3/di_spec.ts b/packages/core/test/render3/di_spec.ts index a3b884cbdc..c266743e03 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -1501,7 +1501,7 @@ describe('di', () => { it('should handle initial undefined state', () => { const contentView = createLViewData( null !, createTView(-1, null, 1, 0, null, null, null), null, LViewFlags.CheckAlways); - const oldView = enterView(contentView, null !); + const oldView = enterView(contentView, null); try { const parent = createLNode(0, TNodeType.Element, null, null, null, null);