diff --git a/modules/@angular/core/src/view/element.ts b/modules/@angular/core/src/view/element.ts index d2f9e57d96..630f9de722 100644 --- a/modules/@angular/core/src/view/element.ts +++ b/modules/@angular/core/src/view/element.ts @@ -31,7 +31,6 @@ export function anchorDef( return { // will bet set by the view definition index: undefined, - reverseChildIndex: undefined, parent: undefined, renderParent: undefined, bindingIndex: undefined, @@ -39,6 +38,7 @@ export function anchorDef( // regular values flags, childFlags: 0, + directChildFlags: 0, childMatchedQueries: 0, matchedQueries, matchedQueryIds, references, ngContentIndex, childCount, bindings: [], outputs: [], @@ -131,7 +131,6 @@ export function elementDef( return { // will bet set by the view definition index: undefined, - reverseChildIndex: undefined, parent: undefined, renderParent: undefined, bindingIndex: undefined, @@ -139,6 +138,7 @@ export function elementDef( // regular values flags, childFlags: 0, + directChildFlags: 0, childMatchedQueries: 0, matchedQueries, matchedQueryIds, references, ngContentIndex, childCount, bindings: bindingDefs, outputs: outputDefs, diff --git a/modules/@angular/core/src/view/ng_content.ts b/modules/@angular/core/src/view/ng_content.ts index 8acbd1898c..d22c4cb800 100644 --- a/modules/@angular/core/src/view/ng_content.ts +++ b/modules/@angular/core/src/view/ng_content.ts @@ -13,7 +13,6 @@ export function ngContentDef(ngContentIndex: number, index: number): NodeDef { return { // will bet set by the view definition index: undefined, - reverseChildIndex: undefined, parent: undefined, renderParent: undefined, bindingIndex: undefined, @@ -21,6 +20,7 @@ export function ngContentDef(ngContentIndex: number, index: number): NodeDef { // regular values flags: NodeFlags.TypeNgContent, childFlags: 0, + directChildFlags: 0, childMatchedQueries: 0, matchedQueries: {}, matchedQueryIds: 0, diff --git a/modules/@angular/core/src/view/provider.ts b/modules/@angular/core/src/view/provider.ts index 69e709d3ef..b5ee345852 100644 --- a/modules/@angular/core/src/view/provider.ts +++ b/modules/@angular/core/src/view/provider.ts @@ -94,7 +94,6 @@ export function _def( return { // will bet set by the view definition index: undefined, - reverseChildIndex: undefined, parent: undefined, renderParent: undefined, bindingIndex: undefined, @@ -102,6 +101,7 @@ export function _def( // regular values flags, childFlags: 0, + directChildFlags: 0, childMatchedQueries: 0, matchedQueries, matchedQueryIds, references, ngContentIndex: undefined, childCount, bindings, outputs, element: undefined, @@ -432,25 +432,43 @@ export function callLifecycleHooksChildrenFirst(view: ViewData, lifecycles: Node if (!(view.def.nodeFlags & lifecycles)) { return; } - const len = view.def.nodes.length; - for (let i = 0; i < len; i++) { - // We use the reverse child oreder to call providers of children first. - const nodeDef = view.def.reverseChildNodes[i]; - const nodeIndex = nodeDef.index; - if (nodeDef.flags & lifecycles) { - // a leaf - Services.setCurrentNode(view, nodeIndex); - callProviderLifecycles(asProviderData(view, nodeIndex).instance, nodeDef.flags & lifecycles); - } else if ((nodeDef.childFlags & lifecycles) === 0) { - // a parent with leafs - // no child matches one of the lifecycles, - // then skip the children + const nodes = view.def.nodes; + for (let i = 0; i < nodes.length; i++) { + const nodeDef = nodes[i]; + let parent = nodeDef.parent; + if (!parent && nodeDef.flags & lifecycles) { + // matching root node (e.g. a pipe) + callProviderLifecycles(view, i, nodeDef.flags & lifecycles); + } + if ((nodeDef.childFlags & lifecycles) === 0) { + // no child matches one of the lifecycles i += nodeDef.childCount; } + while (parent && (parent.flags & NodeFlags.TypeElement) && + i === parent.index + parent.childCount) { + // last child of an element + if (parent.directChildFlags & lifecycles) { + callElementProvidersLifecycles(view, parent, lifecycles); + } + parent = parent.parent; + } } } -function callProviderLifecycles(provider: any, lifecycles: NodeFlags) { +function callElementProvidersLifecycles(view: ViewData, elDef: NodeDef, lifecycles: NodeFlags) { + for (let i = elDef.index + 1; i <= elDef.index + elDef.childCount; i++) { + const nodeDef = view.def.nodes[i]; + if (nodeDef.flags & lifecycles) { + callProviderLifecycles(view, i, nodeDef.flags & lifecycles); + } + // only visit direct children + i += nodeDef.childCount; + } +} + +function callProviderLifecycles(view: ViewData, index: number, lifecycles: NodeFlags) { + const provider = asProviderData(view, index).instance; + Services.setCurrentNode(view, index); if (lifecycles & NodeFlags.AfterContentInit) { provider.ngAfterContentInit(); } diff --git a/modules/@angular/core/src/view/pure_expression.ts b/modules/@angular/core/src/view/pure_expression.ts index 819066d512..c35e1e6272 100644 --- a/modules/@angular/core/src/view/pure_expression.ts +++ b/modules/@angular/core/src/view/pure_expression.ts @@ -38,7 +38,6 @@ function _pureExpressionDef(flags: NodeFlags, propertyNames: string[]): NodeDef return { // will bet set by the view definition index: undefined, - reverseChildIndex: undefined, parent: undefined, renderParent: undefined, bindingIndex: undefined, @@ -46,6 +45,7 @@ function _pureExpressionDef(flags: NodeFlags, propertyNames: string[]): NodeDef // regular values flags, childFlags: 0, + directChildFlags: 0, childMatchedQueries: 0, matchedQueries: {}, matchedQueryIds: 0, diff --git a/modules/@angular/core/src/view/query.ts b/modules/@angular/core/src/view/query.ts index 40a721c6e7..b59b3ac103 100644 --- a/modules/@angular/core/src/view/query.ts +++ b/modules/@angular/core/src/view/query.ts @@ -26,7 +26,6 @@ export function queryDef( return { // will bet set by the view definition index: undefined, - reverseChildIndex: undefined, parent: undefined, renderParent: undefined, bindingIndex: undefined, @@ -34,6 +33,7 @@ export function queryDef( // regular values flags, childFlags: 0, + directChildFlags: 0, childMatchedQueries: 0, ngContentIndex: undefined, matchedQueries: {}, diff --git a/modules/@angular/core/src/view/text.ts b/modules/@angular/core/src/view/text.ts index 5d1bcb96b7..ae25850991 100644 --- a/modules/@angular/core/src/view/text.ts +++ b/modules/@angular/core/src/view/text.ts @@ -30,7 +30,6 @@ export function textDef(ngContentIndex: number, constants: string[]): NodeDef { return { // will bet set by the view definition index: undefined, - reverseChildIndex: undefined, parent: undefined, renderParent: undefined, bindingIndex: undefined, @@ -38,6 +37,7 @@ export function textDef(ngContentIndex: number, constants: string[]): NodeDef { // regular values flags, childFlags: 0, + directChildFlags: 0, childMatchedQueries: 0, matchedQueries: {}, matchedQueryIds: 0, diff --git a/modules/@angular/core/src/view/types.ts b/modules/@angular/core/src/view/types.ts index 9ffff090bb..ccda1440e6 100644 --- a/modules/@angular/core/src/view/types.ts +++ b/modules/@angular/core/src/view/types.ts @@ -33,11 +33,7 @@ export interface ViewDefinition { nodes: NodeDef[]; /** aggregated NodeFlags for all nodes **/ nodeFlags: NodeFlags; - /** - * Order: parents before children, but children in reverse order. - * Especially providers are after elements / anchors. - */ - reverseChildNodes: NodeDef[]; + rootNodeFlags: NodeFlags; lastRenderRootNode: NodeDef; bindingCount: number; outputCount: number; @@ -83,15 +79,16 @@ export const enum ViewFlags { export interface NodeDef { flags: NodeFlags; index: number; - reverseChildIndex: number; parent: NodeDef; renderParent: NodeDef; /** this is checked against NgContentDef.index to find matched nodes */ ngContentIndex: number; /** number of transitive children */ childCount: number; - /** aggregated NodeFlags for all children (does not include self) **/ + /** aggregated NodeFlags for all transitive children (does not include self) **/ childFlags: NodeFlags; + /** aggregated NodeFlags for all direct children (does not include self) **/ + directChildFlags: NodeFlags; bindingIndex: number; bindings: BindingDef[]; diff --git a/modules/@angular/core/src/view/view.ts b/modules/@angular/core/src/view/view.ts index 9813f0af9f..faa0509e94 100644 --- a/modules/@angular/core/src/view/view.ts +++ b/modules/@angular/core/src/view/view.ts @@ -33,6 +33,7 @@ export function viewDef( let viewBindingCount = 0; let viewDisposableCount = 0; let viewNodeFlags = 0; + let viewRootNodeFlags = 0; let viewMatchedQueries = 0; let currentParent: NodeDef = null; let currentElementHasPublicProviders = false; @@ -52,8 +53,6 @@ export function viewDef( node.parent = currentParent; node.bindingIndex = viewBindingCount; node.outputIndex = viewDisposableCount; - node.reverseChildIndex = - calculateReverseChildIndex(currentParent, i, node.childCount, nodes.length); // renderParent needs to account for ng-container! let currentRenderParent: NodeDef; @@ -74,7 +73,6 @@ export function viewDef( currentElementHasPublicProviders = false; currentElementHasPrivateProviders = false; } - reverseChildNodes[node.reverseChildIndex] = node; validateNode(currentParent, node, nodes.length); viewNodeFlags |= node.flags; @@ -84,10 +82,13 @@ export function viewDef( } if (currentParent) { currentParent.childFlags |= node.flags; + currentParent.directChildFlags |= node.flags; currentParent.childMatchedQueries |= node.matchedQueryIds; if (node.element && node.element.template) { currentParent.childMatchedQueries |= node.element.template.nodeMatchedQueries; } + } else { + viewRootNodeFlags |= node.flags; } viewBindingCount += node.bindings.length; @@ -136,8 +137,9 @@ export function viewDef( nodes[nodeIndex].element.handleEvent(view, eventName, event); return { nodeFlags: viewNodeFlags, + rootNodeFlags: viewRootNodeFlags, nodeMatchedQueries: viewMatchedQueries, flags, - nodes: nodes, reverseChildNodes, + nodes: nodes, updateDirectives: updateDirectives || NOOP, updateRenderer: updateRenderer || NOOP, handleEvent: handleEvent || NOOP, @@ -146,47 +148,6 @@ export function viewDef( }; } - - -function calculateReverseChildIndex( - currentParent: NodeDef, i: number, childCount: number, nodeCount: number) { - // Notes about reverse child order: - // - Every node is directly before its children, in dfs and reverse child order. - // - node.childCount contains all children, in dfs and reverse child order. - // - In dfs order, every node is before its first child - // - In reverse child order, every node is before its last child - - // Algorithm, main idea: - // - In reverse child order, the ranges for each child + its transitive children are mirrored - // regarding their position inside of their parent - - // Visualization: - // Given the following tree: - // Nodes: n0 - // n1 n2 - // n11 n12 n21 n22 - // dfs: 0 1 2 3 4 5 6 - // result: 0 4 6 5 1 3 2 - // - // Example: - // Current node = 1 - // 1) lastChildIndex = 3 - // 2) lastChildOffsetRelativeToParentInDfsOrder = 2 - // 3) parentEndIndexInReverseChildOrder = 6 - // 4) result = 4 - let lastChildOffsetRelativeToParentInDfsOrder: number; - let parentEndIndexInReverseChildOrder: number; - if (currentParent) { - const lastChildIndex = i + childCount; - lastChildOffsetRelativeToParentInDfsOrder = lastChildIndex - currentParent.index - 1; - parentEndIndexInReverseChildOrder = currentParent.reverseChildIndex + currentParent.childCount; - } else { - lastChildOffsetRelativeToParentInDfsOrder = i + childCount; - parentEndIndexInReverseChildOrder = nodeCount - 1; - } - return parentEndIndexInReverseChildOrder - lastChildOffsetRelativeToParentInDfsOrder; -} - function validateNode(parent: NodeDef, node: NodeDef, nodeCount: number) { const template = node.element && node.element.template; if (template) { diff --git a/modules/@angular/core/test/linker/change_detection_integration_spec.ts b/modules/@angular/core/test/linker/change_detection_integration_spec.ts index e969431f72..91790b3d90 100644 --- a/modules/@angular/core/test/linker/change_detection_integration_spec.ts +++ b/modules/@angular/core/test/linker/change_detection_integration_spec.ts @@ -906,12 +906,13 @@ function createTests({viewEngine}: {viewEngine: boolean}) { it('should be called in reverse order so the child is always notified before the parent', fakeAsync(() => { const ctx = createCompFixture( - '