fix(ivy): support property values changed in ngOnChanges (forward rref case) (#29054)
PR Close #29054
This commit is contained in:

committed by
Andrew Kushnir

parent
6215799055
commit
25166d4f41
@ -104,6 +104,7 @@ export {
|
||||
elementStyleProp as ɵelementStyleProp,
|
||||
elementStylingApply as ɵelementStylingApply,
|
||||
elementClassProp as ɵelementClassProp,
|
||||
flushHooksUpTo as ɵflushHooksUpTo,
|
||||
textBinding as ɵtextBinding,
|
||||
template as ɵtemplate,
|
||||
embeddedViewEnd as ɵembeddedViewEnd,
|
||||
|
@ -29,7 +29,7 @@ import {renderInitialClasses, renderInitialStyles} from './styling/class_and_sty
|
||||
import {publishDefaultGlobalUtils} from './util/global_utils';
|
||||
import {defaultScheduler, renderStringify} from './util/misc_utils';
|
||||
import {getRootContext, getRootView} from './util/view_traversal_utils';
|
||||
import {readPatchedLView} from './util/view_utils';
|
||||
import {readPatchedLView, resetPreOrderHookFlags} from './util/view_utils';
|
||||
|
||||
|
||||
|
||||
@ -142,6 +142,7 @@ export function renderComponent<T>(
|
||||
|
||||
refreshDescendantViews(rootView); // creation mode pass
|
||||
rootView[FLAGS] &= ~LViewFlags.CreationMode;
|
||||
resetPreOrderHookFlags(rootView);
|
||||
refreshDescendantViews(rootView); // update mode pass
|
||||
} finally {
|
||||
leaveView(oldView);
|
||||
@ -248,7 +249,7 @@ export function LifecycleHooksFeature(component: any, def: ComponentDef<any>): v
|
||||
const rootTView = readPatchedLView(component) ![TVIEW];
|
||||
const dirIndex = rootTView.data.length - 1;
|
||||
|
||||
registerPreOrderHooks(dirIndex, def, rootTView);
|
||||
registerPreOrderHooks(dirIndex, def, rootTView, -1, -1, -1);
|
||||
// TODO(misko): replace `as TNode` with createTNode call. (needs refactoring to lose dep on
|
||||
// LNode).
|
||||
registerPostOrderHooks(
|
||||
|
@ -10,7 +10,7 @@ import {assertEqual} from '../util/assert';
|
||||
|
||||
import {DirectiveDef} from './interfaces/definition';
|
||||
import {TNode} from './interfaces/node';
|
||||
import {FLAGS, HookData, InitPhaseState, LView, LViewFlags, TView} from './interfaces/view';
|
||||
import {FLAGS, HookData, InitPhaseState, LView, LViewFlags, PREORDER_HOOK_FLAGS, PreOrderHookFlags, TView} from './interfaces/view';
|
||||
|
||||
|
||||
|
||||
@ -19,34 +19,50 @@ import {FLAGS, HookData, InitPhaseState, LView, LViewFlags, TView} from './inter
|
||||
*
|
||||
* Must be run *only* on the first template pass.
|
||||
*
|
||||
* The TView's hooks arrays are arranged in alternating pairs of directiveIndex and hookFunction,
|
||||
* i.e.: `[directiveIndexA, hookFunctionA, directiveIndexB, hookFunctionB, ...]`. For `OnChanges`
|
||||
* hooks, the `directiveIndex` will be *negative*, signaling {@link callHooks} that the
|
||||
* `hookFunction` must be passed the the appropriate {@link SimpleChanges} object.
|
||||
* Sets up the pre-order hooks on the provided `tView`,
|
||||
* see {@link HookData} for details about the data structure.
|
||||
*
|
||||
* @param directiveIndex The index of the directive in LView
|
||||
* @param directiveDef The definition containing the hooks to setup in tView
|
||||
* @param tView The current TView
|
||||
* @param nodeIndex The index of the node to which the directive is attached
|
||||
* @param initialPreOrderHooksLength the number of pre-order hooks already registered before the
|
||||
* current process, used to know if the node index has to be added to the array. If it is -1,
|
||||
* the node index is never added.
|
||||
* @param initialPreOrderCheckHooksLength same as previous for pre-order check hooks
|
||||
*/
|
||||
export function registerPreOrderHooks(
|
||||
directiveIndex: number, directiveDef: DirectiveDef<any>, tView: TView): void {
|
||||
directiveIndex: number, directiveDef: DirectiveDef<any>, tView: TView, nodeIndex: number,
|
||||
initialPreOrderHooksLength: number, initialPreOrderCheckHooksLength: number): void {
|
||||
ngDevMode &&
|
||||
assertEqual(tView.firstTemplatePass, true, 'Should only be called on first template pass');
|
||||
|
||||
const {onChanges, onInit, doCheck} = directiveDef;
|
||||
if (initialPreOrderHooksLength >= 0 &&
|
||||
(!tView.preOrderHooks || initialPreOrderHooksLength === tView.preOrderHooks.length) &&
|
||||
(onChanges || onInit || doCheck)) {
|
||||
(tView.preOrderHooks || (tView.preOrderHooks = [])).push(nodeIndex);
|
||||
}
|
||||
|
||||
if (initialPreOrderCheckHooksLength >= 0 &&
|
||||
(!tView.preOrderCheckHooks ||
|
||||
initialPreOrderCheckHooksLength === tView.preOrderCheckHooks.length) &&
|
||||
(onChanges || doCheck)) {
|
||||
(tView.preOrderCheckHooks || (tView.preOrderCheckHooks = [])).push(nodeIndex);
|
||||
}
|
||||
|
||||
if (onChanges) {
|
||||
(tView.initHooks || (tView.initHooks = [])).push(directiveIndex, onChanges);
|
||||
(tView.checkHooks || (tView.checkHooks = [])).push(directiveIndex, onChanges);
|
||||
(tView.preOrderHooks || (tView.preOrderHooks = [])).push(directiveIndex, onChanges);
|
||||
(tView.preOrderCheckHooks || (tView.preOrderCheckHooks = [])).push(directiveIndex, onChanges);
|
||||
}
|
||||
|
||||
if (onInit) {
|
||||
(tView.initHooks || (tView.initHooks = [])).push(-directiveIndex, onInit);
|
||||
(tView.preOrderHooks || (tView.preOrderHooks = [])).push(-directiveIndex, onInit);
|
||||
}
|
||||
|
||||
if (doCheck) {
|
||||
(tView.initHooks || (tView.initHooks = [])).push(directiveIndex, doCheck);
|
||||
(tView.checkHooks || (tView.checkHooks = [])).push(directiveIndex, doCheck);
|
||||
(tView.preOrderHooks || (tView.preOrderHooks = [])).push(directiveIndex, doCheck);
|
||||
(tView.preOrderCheckHooks || (tView.preOrderCheckHooks = [])).push(directiveIndex, doCheck);
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,9 +75,8 @@ export function registerPreOrderHooks(
|
||||
* preserve hook execution order. Content, view, and destroy hooks for projected
|
||||
* components and directives must be called *before* their hosts.
|
||||
*
|
||||
* Sets up the content, view, and destroy hooks on the provided `tView` such that
|
||||
* they're added in alternating pairs of directiveIndex and hookFunction,
|
||||
* i.e.: `[directiveIndexA, hookFunctionA, directiveIndexB, hookFunctionB, ...]`
|
||||
* Sets up the content, view, and destroy hooks on the provided `tView`,
|
||||
* see {@link HookData} for details about the data structure.
|
||||
*
|
||||
* NOTE: This does not set up `onChanges`, `onInit` or `doCheck`, those are set up
|
||||
* separately at `elementStart`.
|
||||
@ -103,25 +118,49 @@ export function registerPostOrderHooks(tView: TView, tNode: TNode): void {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executing hooks requires complex logic as we need to deal with 2 constraints.
|
||||
*
|
||||
* 1. Init hooks (ngOnInit, ngAfterContentInit, ngAfterViewInit) must all be executed once and only
|
||||
* once, across many change detection cycles. This must be true even if some hooks throw, or if
|
||||
* some recursively trigger a change detection cycle.
|
||||
* To solve that, it is required to track the state of the execution of these init hooks.
|
||||
* This is done by storing and maintaining flags in the view: the {@link InitPhaseState},
|
||||
* and the index within that phase. They can be seen as a cursor in the following structure:
|
||||
* [[onInit1, onInit2], [afterContentInit1], [afterViewInit1, afterViewInit2, afterViewInit3]]
|
||||
* They are are stored as flags in LView[FLAGS].
|
||||
*
|
||||
* 2. Pre-order hooks can be executed in batches, because of the flushHooksUpTo instruction.
|
||||
* To be able to pause and resume their execution, we also need some state about the hook's array
|
||||
* that is being processed:
|
||||
* - the index of the next hook to be executed
|
||||
* - the number of init hooks already found in the processed part of the array
|
||||
* They are are stored as flags in LView[PREORDER_HOOK_FLAGS].
|
||||
*/
|
||||
|
||||
/**
|
||||
* Executes necessary hooks at the start of executing a template.
|
||||
*
|
||||
* Executes hooks that are to be run during the initialization of a directive such
|
||||
* as `onChanges`, `onInit`, and `doCheck`.
|
||||
*
|
||||
* Has the side effect of updating the RunInit flag in `lView` to be `0`, so that
|
||||
* this isn't run a second time.
|
||||
*
|
||||
* @param lView The current view
|
||||
* @param tView Static data for the view containing the hooks to be executed
|
||||
* @param checkNoChangesMode Whether or not we're in checkNoChanges mode.
|
||||
* @param @param currentNodeIndex 2 cases depending the the value:
|
||||
* - undefined: execute hooks only from the saved index until the end of the array (pre-order case,
|
||||
* when flushing the remaining hooks)
|
||||
* - number: execute hooks only from the saved index until that node index exclusive (pre-order
|
||||
* case, when executing flushHooksUpTo(number))
|
||||
*/
|
||||
export function executeInitHooks(
|
||||
currentView: LView, tView: TView, checkNoChangesMode: boolean): void {
|
||||
export function executePreOrderHooks(
|
||||
currentView: LView, tView: TView, checkNoChangesMode: boolean,
|
||||
currentNodeIndex: number | undefined): void {
|
||||
if (!checkNoChangesMode) {
|
||||
executeHooks(
|
||||
currentView, tView.initHooks, tView.checkHooks, checkNoChangesMode,
|
||||
InitPhaseState.OnInitHooksToBeRun);
|
||||
currentView, tView.preOrderHooks, tView.preOrderCheckHooks, checkNoChangesMode,
|
||||
InitPhaseState.OnInitHooksToBeRun,
|
||||
currentNodeIndex !== undefined ? currentNodeIndex : null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -129,24 +168,33 @@ export function executeInitHooks(
|
||||
* Executes hooks against the given `LView` based off of whether or not
|
||||
* This is the first pass.
|
||||
*
|
||||
* @param lView The view instance data to run the hooks against
|
||||
* @param currentView The view instance data to run the hooks against
|
||||
* @param firstPassHooks An array of hooks to run if we're in the first view pass
|
||||
* @param checkHooks An Array of hooks to run if we're not in the first view pass.
|
||||
* @param checkNoChangesMode Whether or not we're in no changes mode.
|
||||
* @param initPhaseState the current state of the init phase
|
||||
* @param currentNodeIndex 3 cases depending the the value:
|
||||
* - undefined: all hooks from the array should be executed (post-order case)
|
||||
* - null: execute hooks only from the saved index until the end of the array (pre-order case, when
|
||||
* flushing the remaining hooks)
|
||||
* - number: execute hooks only from the saved index until that node index exclusive (pre-order
|
||||
* case, when executing flushHooksUpTo(number))
|
||||
*/
|
||||
export function executeHooks(
|
||||
currentView: LView, firstPassHooks: HookData | null, checkHooks: HookData | null,
|
||||
checkNoChangesMode: boolean, initPhase: number): void {
|
||||
checkNoChangesMode: boolean, initPhaseState: InitPhaseState,
|
||||
currentNodeIndex: number | null | undefined): void {
|
||||
if (checkNoChangesMode) return;
|
||||
const hooksToCall = (currentView[FLAGS] & LViewFlags.InitPhaseStateMask) === initPhase ?
|
||||
const hooksToCall = (currentView[FLAGS] & LViewFlags.InitPhaseStateMask) === initPhaseState ?
|
||||
firstPassHooks :
|
||||
checkHooks;
|
||||
if (hooksToCall) {
|
||||
callHooks(currentView, hooksToCall, initPhase);
|
||||
callHooks(currentView, hooksToCall, initPhaseState, currentNodeIndex);
|
||||
}
|
||||
// The init phase state must be always checked here as it may have been recursively updated
|
||||
if ((currentView[FLAGS] & LViewFlags.InitPhaseStateMask) === initPhase &&
|
||||
initPhase !== InitPhaseState.InitPhaseCompleted) {
|
||||
if (currentNodeIndex == null &&
|
||||
(currentView[FLAGS] & LViewFlags.InitPhaseStateMask) === initPhaseState &&
|
||||
initPhaseState !== InitPhaseState.InitPhaseCompleted) {
|
||||
currentView[FLAGS] &= LViewFlags.IndexWithinInitPhaseReset;
|
||||
currentView[FLAGS] += LViewFlags.InitPhaseStateIncrementer;
|
||||
}
|
||||
@ -158,25 +206,68 @@ export function executeHooks(
|
||||
*
|
||||
* @param currentView The current view
|
||||
* @param arr The array in which the hooks are found
|
||||
* @param initPhaseState the current state of the init phase
|
||||
* @param currentNodeIndex 3 cases depending the the value:
|
||||
* - undefined: all hooks from the array should be executed (post-order case)
|
||||
* - null: execute hooks only from the saved index until the end of the array (pre-order case, when
|
||||
* flushing the remaining hooks)
|
||||
* - number: execute hooks only from the saved index until that node index exclusive (pre-order
|
||||
* case, when executing flushHooksUpTo(number))
|
||||
*/
|
||||
export function callHooks(currentView: LView, arr: HookData, initPhase?: number): void {
|
||||
let initHooksCount = 0;
|
||||
for (let i = 0; i < arr.length; i += 2) {
|
||||
const isInitHook = arr[i] < 0;
|
||||
const directiveIndex = isInitHook ? -arr[i] : arr[i] as number;
|
||||
const directive = currentView[directiveIndex];
|
||||
function callHooks(
|
||||
currentView: LView, arr: HookData, initPhase: InitPhaseState,
|
||||
currentNodeIndex: number | null | undefined): void {
|
||||
const startIndex = currentNodeIndex !== undefined ?
|
||||
(currentView[PREORDER_HOOK_FLAGS] & PreOrderHookFlags.IndexOfTheNextPreOrderHookMaskMask) :
|
||||
0;
|
||||
const nodeIndexLimit = currentNodeIndex != null ? currentNodeIndex : -1;
|
||||
let lastNodeIndexFound = 0;
|
||||
for (let i = startIndex; i < arr.length; i++) {
|
||||
const hook = arr[i + 1] as() => void;
|
||||
if (isInitHook) {
|
||||
initHooksCount++;
|
||||
const indexWithintInitPhase = currentView[FLAGS] >> LViewFlags.IndexWithinInitPhaseShift;
|
||||
// The init phase state must be always checked here as it may have been recursively updated
|
||||
if (indexWithintInitPhase < initHooksCount &&
|
||||
(currentView[FLAGS] & LViewFlags.InitPhaseStateMask) === initPhase) {
|
||||
currentView[FLAGS] += LViewFlags.IndexWithinInitPhaseIncrementer;
|
||||
hook.call(directive);
|
||||
if (typeof hook === 'number') {
|
||||
lastNodeIndexFound = arr[i] as number;
|
||||
if (currentNodeIndex != null && lastNodeIndexFound >= currentNodeIndex) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
hook.call(directive);
|
||||
const isInitHook = arr[i] < 0;
|
||||
if (isInitHook)
|
||||
currentView[PREORDER_HOOK_FLAGS] += PreOrderHookFlags.NumberOfInitHooksCalledIncrementer;
|
||||
if (lastNodeIndexFound < nodeIndexLimit || nodeIndexLimit == -1) {
|
||||
callHook(currentView, initPhase, arr, i);
|
||||
currentView[PREORDER_HOOK_FLAGS] =
|
||||
(currentView[PREORDER_HOOK_FLAGS] & PreOrderHookFlags.NumberOfInitHooksCalledMask) + i +
|
||||
2;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute one hook against the current `LView`.
|
||||
*
|
||||
* @param currentView The current view
|
||||
* @param initPhaseState the current state of the init phase
|
||||
* @param arr The array in which the hooks are found
|
||||
* @param i The current index within the hook data array
|
||||
*/
|
||||
function callHook(currentView: LView, initPhase: InitPhaseState, arr: HookData, i: number) {
|
||||
const isInitHook = arr[i] < 0;
|
||||
const hook = arr[i + 1] as() => void;
|
||||
const directiveIndex = isInitHook ? -arr[i] : arr[i] as number;
|
||||
const directive = currentView[directiveIndex];
|
||||
if (isInitHook) {
|
||||
const indexWithintInitPhase = currentView[FLAGS] >> LViewFlags.IndexWithinInitPhaseShift;
|
||||
// The init phase state must be always checked here as it may have been recursively
|
||||
// updated
|
||||
if (indexWithintInitPhase <
|
||||
(currentView[PREORDER_HOOK_FLAGS] >> PreOrderHookFlags.NumberOfInitHooksCalledShift) &&
|
||||
(currentView[FLAGS] & LViewFlags.InitPhaseStateMask) === initPhase) {
|
||||
currentView[FLAGS] += LViewFlags.IndexWithinInitPhaseIncrementer;
|
||||
hook.call(directive);
|
||||
}
|
||||
} else {
|
||||
hook.call(directive);
|
||||
}
|
||||
}
|
||||
|
@ -57,6 +57,8 @@ export {
|
||||
elementStyleProp,
|
||||
elementStylingApply,
|
||||
|
||||
flushHooksUpTo,
|
||||
|
||||
listener,
|
||||
store,
|
||||
load,
|
||||
|
@ -24,7 +24,7 @@ import {attachPatchData, getComponentViewByInstance} from './context_discovery';
|
||||
import {attachLContainerDebug, attachLViewDebug} from './debug';
|
||||
import {diPublicInInjector, getNodeInjectable, getOrCreateInjectable, getOrCreateNodeInjectorForNode, injectAttributeImpl} from './di';
|
||||
import {throwMultipleComponentError} from './errors';
|
||||
import {executeHooks, executeInitHooks, registerPostOrderHooks, registerPreOrderHooks} from './hooks';
|
||||
import {executeHooks, executePreOrderHooks, registerPostOrderHooks, registerPreOrderHooks} from './hooks';
|
||||
import {ACTIVE_INDEX, LContainer, VIEWS} from './interfaces/container';
|
||||
import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, PipeDefListOrFactory, RenderFlags, ViewQueriesFunction} from './interfaces/definition';
|
||||
import {INJECTOR_BLOOM_PARENT_SIZE, NodeInjectorFactory} from './interfaces/injector';
|
||||
@ -48,7 +48,7 @@ import {NO_CHANGE} from './tokens';
|
||||
import {attrsStylingIndexOf, setUpAttributes} from './util/attrs_utils';
|
||||
import {INTERPOLATION_DELIMITER, renderStringify} from './util/misc_utils';
|
||||
import {findComponentView, getLViewParent, getRootContext, getRootView} from './util/view_traversal_utils';
|
||||
import {getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getTNode, isComponent, isComponentDef, isContentQueryHost, isRootView, loadInternal, readPatchedLView, unwrapRNode, viewAttachedToChangeDetector} from './util/view_utils';
|
||||
import {getComponentViewByIndex, getNativeByIndex, getNativeByTNode, getTNode, isComponent, isComponentDef, isContentQueryHost, isRootView, loadInternal, readPatchedLView, resetPreOrderHookFlags, unwrapRNode, viewAttachedToChangeDetector} from './util/view_utils';
|
||||
|
||||
|
||||
|
||||
@ -84,16 +84,17 @@ export function refreshDescendantViews(lView: LView) {
|
||||
if (!creationMode) {
|
||||
const checkNoChangesMode = getCheckNoChangesMode();
|
||||
|
||||
executeInitHooks(lView, tView, checkNoChangesMode);
|
||||
executePreOrderHooks(lView, tView, checkNoChangesMode, undefined);
|
||||
|
||||
refreshDynamicEmbeddedViews(lView);
|
||||
|
||||
// Content query results must be refreshed before content hooks are called.
|
||||
refreshContentQueries(tView, lView);
|
||||
|
||||
resetPreOrderHookFlags(lView);
|
||||
executeHooks(
|
||||
lView, tView.contentHooks, tView.contentCheckHooks, checkNoChangesMode,
|
||||
InitPhaseState.AfterContentInitHooksToBeRun);
|
||||
InitPhaseState.AfterContentInitHooksToBeRun, undefined);
|
||||
|
||||
setHostBindings(tView, lView);
|
||||
}
|
||||
@ -180,6 +181,7 @@ export function createLView<T>(
|
||||
const lView = tView.blueprint.slice() as LView;
|
||||
lView[HOST] = host;
|
||||
lView[FLAGS] = flags | LViewFlags.CreationMode | LViewFlags.Attached | LViewFlags.FirstLViewPass;
|
||||
resetPreOrderHookFlags(lView);
|
||||
lView[PARENT] = lView[DECLARATION_VIEW] = parentLView;
|
||||
lView[CONTEXT] = context;
|
||||
lView[RENDERER_FACTORY] = (rendererFactory || parentLView && parentLView[RENDERER_FACTORY]) !;
|
||||
@ -405,6 +407,7 @@ export function renderEmbeddedTemplate<T>(viewToRender: LView, tView: TView, con
|
||||
setPreviousOrParentTNode(null !);
|
||||
|
||||
oldView = enterView(viewToRender, viewToRender[T_HOST]);
|
||||
resetPreOrderHookFlags(viewToRender);
|
||||
namespaceHTML();
|
||||
tView.template !(getRenderFlags(viewToRender), context);
|
||||
// This must be set to false immediately after the first creation run because in an
|
||||
@ -459,6 +462,7 @@ function renderComponentOrTemplate<T>(
|
||||
}
|
||||
|
||||
// update mode pass
|
||||
resetPreOrderHookFlags(hostView);
|
||||
templateFn && templateFn(RenderFlags.Update, context);
|
||||
refreshDescendantViews(hostView);
|
||||
} finally {
|
||||
@ -807,8 +811,8 @@ export function createTView(
|
||||
firstTemplatePass: true,
|
||||
staticViewQueries: false,
|
||||
staticContentQueries: false,
|
||||
initHooks: null,
|
||||
checkHooks: null,
|
||||
preOrderHooks: null,
|
||||
preOrderCheckHooks: null,
|
||||
contentHooks: null,
|
||||
contentCheckHooks: null,
|
||||
viewHooks: null,
|
||||
@ -1056,6 +1060,17 @@ export function elementEnd(): void {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Flushes all the lifecycle hooks for directives up until (and excluding) that node index
|
||||
*
|
||||
* @param index The index of the element in the `LView`
|
||||
*/
|
||||
export function flushHooksUpTo(index: number): void {
|
||||
const lView = getLView();
|
||||
executePreOrderHooks(lView, lView[TVIEW], getCheckNoChangesMode(), index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the value of removes an attribute on an Element.
|
||||
*
|
||||
@ -1748,6 +1763,10 @@ function resolveDirectives(
|
||||
if (def.providersResolver) def.providersResolver(def);
|
||||
}
|
||||
generateExpandoInstructionBlock(tView, tNode, directives.length);
|
||||
const initialPreOrderHooksLength = (tView.preOrderHooks && tView.preOrderHooks.length) || 0;
|
||||
const initialPreOrderCheckHooksLength =
|
||||
(tView.preOrderCheckHooks && tView.preOrderCheckHooks.length) || 0;
|
||||
const nodeIndex = tNode.index - HEADER_OFFSET;
|
||||
for (let i = 0; i < directives.length; i++) {
|
||||
const def = directives[i] as DirectiveDef<any>;
|
||||
|
||||
@ -1758,7 +1777,9 @@ function resolveDirectives(
|
||||
|
||||
// Init hooks are queued now so ngOnInit is called in host components before
|
||||
// any projected components.
|
||||
registerPreOrderHooks(directiveDefIdx, def, tView);
|
||||
registerPreOrderHooks(
|
||||
directiveDefIdx, def, tView, nodeIndex, initialPreOrderHooksLength,
|
||||
initialPreOrderCheckHooksLength);
|
||||
}
|
||||
}
|
||||
if (exportsMap) cacheMatchingLocalNames(tNode, localRefs, exportsMap);
|
||||
@ -2276,7 +2297,7 @@ export function containerRefreshStart(index: number): void {
|
||||
|
||||
// We need to execute init hooks here so ngOnInit hooks are called in top level views
|
||||
// before they are called in embedded views (for backwards compatibility).
|
||||
executeInitHooks(lView, tView, getCheckNoChangesMode());
|
||||
executePreOrderHooks(lView, tView, getCheckNoChangesMode(), undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2440,6 +2461,7 @@ export function embeddedViewEnd(): void {
|
||||
refreshDescendantViews(lView); // creation mode pass
|
||||
lView[FLAGS] &= ~LViewFlags.CreationMode;
|
||||
}
|
||||
resetPreOrderHookFlags(lView);
|
||||
refreshDescendantViews(lView); // update mode pass
|
||||
const lContainer = lView[PARENT] as LContainer;
|
||||
ngDevMode && assertLContainerOrUndefined(lContainer);
|
||||
@ -2834,6 +2856,7 @@ export function checkView<T>(hostView: LView, component: T) {
|
||||
const creationMode = isCreationMode(hostView);
|
||||
|
||||
try {
|
||||
resetPreOrderHookFlags(hostView);
|
||||
namespaceHTML();
|
||||
creationMode && executeViewQueryFn(RenderFlags.Create, hostTView, component);
|
||||
templateFn(getRenderFlags(hostView), component);
|
||||
|
@ -45,8 +45,9 @@ export const CHILD_HEAD = 14;
|
||||
export const CHILD_TAIL = 15;
|
||||
export const CONTENT_QUERIES = 16;
|
||||
export const DECLARATION_VIEW = 17;
|
||||
export const PREORDER_HOOK_FLAGS = 18;
|
||||
/** Size of LView's header. Necessary to adjust for it when setting slots. */
|
||||
export const HEADER_OFFSET = 19;
|
||||
export const HEADER_OFFSET = 20;
|
||||
|
||||
|
||||
// This interface replaces the real LView interface if it is an arg or a
|
||||
@ -215,6 +216,11 @@ export interface LView extends Array<any> {
|
||||
* context.
|
||||
*/
|
||||
[DECLARATION_VIEW]: LView|null;
|
||||
|
||||
/**
|
||||
* More flags for this view. See PreOrderHookFlags for more info.
|
||||
*/
|
||||
[PREORDER_HOOK_FLAGS]: PreOrderHookFlags;
|
||||
}
|
||||
|
||||
/** Flags associated with an LView (saved in LView[FLAGS]) */
|
||||
@ -296,6 +302,20 @@ export const enum InitPhaseState {
|
||||
InitPhaseCompleted = 0b11,
|
||||
}
|
||||
|
||||
/** More flags associated with an LView (saved in LView[FLAGS_MORE]) */
|
||||
export const enum PreOrderHookFlags {
|
||||
/** The index of the next pre-order hook to be called in the hooks array, on the first 16
|
||||
bits */
|
||||
IndexOfTheNextPreOrderHookMaskMask = 0b01111111111111111,
|
||||
|
||||
/**
|
||||
* The number of init hooks that have already been called, on the last 16 bits
|
||||
*/
|
||||
NumberOfInitHooksCalledIncrementer = 0b010000000000000000,
|
||||
NumberOfInitHooksCalledShift = 16,
|
||||
NumberOfInitHooksCalledMask = 0b11111111111111110000000000000000,
|
||||
}
|
||||
|
||||
/**
|
||||
* Set of instructions used to process host bindings efficiently.
|
||||
*
|
||||
@ -438,21 +458,21 @@ export interface TView {
|
||||
pipeRegistry: PipeDefList|null;
|
||||
|
||||
/**
|
||||
* Array of ngOnInit and ngDoCheck hooks that should be executed for this view in
|
||||
* Array of ngOnInit, ngOnChanges and ngDoCheck hooks that should be executed for this view in
|
||||
* creation mode.
|
||||
*
|
||||
* Even indices: Directive index
|
||||
* Odd indices: Hook function
|
||||
*/
|
||||
initHooks: HookData|null;
|
||||
preOrderHooks: HookData|null;
|
||||
|
||||
/**
|
||||
* Array of ngDoCheck hooks that should be executed for this view in update mode.
|
||||
* Array of ngOnChanges and ngDoCheck hooks that should be executed for this view in update mode.
|
||||
*
|
||||
* Even indices: Directive index
|
||||
* Odd indices: Hook function
|
||||
*/
|
||||
checkHooks: HookData|null;
|
||||
preOrderCheckHooks: HookData|null;
|
||||
|
||||
/**
|
||||
* Array of ngAfterContentInit and ngAfterContentChecked hooks that should be executed
|
||||
@ -591,8 +611,14 @@ export interface RootContext {
|
||||
/**
|
||||
* Array of hooks that should be executed for a view and their directive indices.
|
||||
*
|
||||
* Even indices: Directive index
|
||||
* Odd indices: Hook function
|
||||
* For each node of the view, the following data is stored:
|
||||
* 1) Node index (optional)
|
||||
* 2) A series of number/function pairs where:
|
||||
* - even indices are directive indices
|
||||
* - odd indices are hook functions
|
||||
*
|
||||
* Special cases:
|
||||
* - a negative directive index flags an init hook (ngOnInit, ngAfterContentInit, ngAfterViewInit)
|
||||
*/
|
||||
export type HookData = (number | (() => void))[];
|
||||
|
||||
|
@ -99,6 +99,7 @@ export const angularCoreEnv: {[name: string]: Function} = {
|
||||
'ɵelementStylingMap': r3.elementStylingMap,
|
||||
'ɵelementStyleProp': r3.elementStyleProp,
|
||||
'ɵelementStylingApply': r3.elementStylingApply,
|
||||
'ɵflushHooksUpTo': r3.flushHooksUpTo,
|
||||
'ɵtemplate': r3.template,
|
||||
'ɵtext': r3.text,
|
||||
'ɵtextBinding': r3.textBinding,
|
||||
|
@ -13,6 +13,7 @@ import {executeHooks} from './hooks';
|
||||
import {ComponentDef, DirectiveDef} from './interfaces/definition';
|
||||
import {TElementNode, TNode, TViewNode} from './interfaces/node';
|
||||
import {BINDING_INDEX, CONTEXT, DECLARATION_VIEW, FLAGS, InitPhaseState, LView, LViewFlags, OpaqueViewState, TVIEW} from './interfaces/view';
|
||||
import {resetPreOrderHookFlags} from './util/view_utils';
|
||||
|
||||
|
||||
|
||||
@ -304,9 +305,10 @@ export function leaveView(newView: LView): void {
|
||||
lView[FLAGS] &= ~LViewFlags.CreationMode;
|
||||
} else {
|
||||
try {
|
||||
resetPreOrderHookFlags(lView);
|
||||
executeHooks(
|
||||
lView, tView.viewHooks, tView.viewCheckHooks, checkNoChangesMode,
|
||||
InitPhaseState.AfterViewInitHooksToBeRun);
|
||||
InitPhaseState.AfterViewInitHooksToBeRun, undefined);
|
||||
} finally {
|
||||
// Views are clean and in update mode after being checked, so these bits are cleared
|
||||
lView[FLAGS] &= ~(LViewFlags.Dirty | LViewFlags.FirstLViewPass);
|
||||
|
@ -13,7 +13,7 @@ import {ComponentDef, DirectiveDef} from '../interfaces/definition';
|
||||
import {TNode, TNodeFlags} from '../interfaces/node';
|
||||
import {RNode} from '../interfaces/renderer';
|
||||
import {StylingContext} from '../interfaces/styling';
|
||||
import {FLAGS, HEADER_OFFSET, HOST, LView, LViewFlags, PARENT, TData, TVIEW} from '../interfaces/view';
|
||||
import {FLAGS, HEADER_OFFSET, HOST, LView, LViewFlags, PARENT, PREORDER_HOOK_FLAGS, TData, TVIEW} from '../interfaces/view';
|
||||
|
||||
|
||||
|
||||
@ -197,3 +197,11 @@ export function viewAttachedToChangeDetector(view: LView): boolean {
|
||||
export function viewAttachedToContainer(view: LView): boolean {
|
||||
return isLContainer(view[PARENT]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the pre-order hook flags of the view.
|
||||
* @param lView the LView on which the flags are reset
|
||||
*/
|
||||
export function resetPreOrderHookFlags(lView: LView) {
|
||||
lView[PREORDER_HOOK_FLAGS] = 0;
|
||||
}
|
||||
|
Reference in New Issue
Block a user