fix(core): call lifecycle hooks for siblings in declaration order

This commit is contained in:
Tobias Bosch
2017-02-27 13:00:49 -08:00
committed by Igor Minar
parent 14d37fe052
commit d2e42567a6
10 changed files with 82 additions and 142 deletions

View File

@ -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,

View File

@ -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,

View File

@ -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();
}

View File

@ -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,

View File

@ -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: {},

View File

@ -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,

View File

@ -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[];

View File

@ -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) {