fix(core): call lifecycle hooks for siblings in declaration order
This commit is contained in:
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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: {},
|
||||
|
@ -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,
|
||||
|
@ -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[];
|
||||
|
@ -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) {
|
||||
|
Reference in New Issue
Block a user