2018-01-23 10:57:48 -08:00
|
|
|
/**
|
|
|
|
* @license
|
|
|
|
* Copyright Google Inc. All Rights Reserved.
|
|
|
|
*
|
|
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
|
|
* found in the LICENSE file at https://angular.io/license
|
|
|
|
*/
|
2018-01-22 17:43:52 -08:00
|
|
|
|
2019-08-14 14:35:36 +02:00
|
|
|
import {assertEqual, assertNotEqual} from '../util/assert';
|
2018-12-20 17:23:25 -08:00
|
|
|
|
2019-11-01 13:06:17 -07:00
|
|
|
import {assertFirstCreatePass} from './assert';
|
2018-09-21 12:12:06 -07:00
|
|
|
import {DirectiveDef} from './interfaces/definition';
|
2018-11-28 15:54:38 -08:00
|
|
|
import {TNode} from './interfaces/node';
|
2019-03-01 14:39:28 +01:00
|
|
|
import {FLAGS, HookData, InitPhaseState, LView, LViewFlags, PREORDER_HOOK_FLAGS, PreOrderHookFlags, TView} from './interfaces/view';
|
2019-08-14 14:35:36 +02:00
|
|
|
import {getCheckNoChangesMode} from './state';
|
2018-06-01 18:54:23 -07:00
|
|
|
|
2018-01-22 17:43:52 -08:00
|
|
|
|
2018-11-28 15:54:38 -08:00
|
|
|
|
2018-01-22 17:43:52 -08:00
|
|
|
/**
|
2018-12-20 17:23:25 -08:00
|
|
|
* Adds all directive lifecycle hooks from the given `DirectiveDef` to the given `TView`.
|
|
|
|
*
|
|
|
|
* Must be run *only* on the first template pass.
|
2018-01-23 10:57:48 -08:00
|
|
|
*
|
2019-03-01 14:39:28 +01:00
|
|
|
* Sets up the pre-order hooks on the provided `tView`,
|
|
|
|
* see {@link HookData} for details about the data structure.
|
2018-01-23 10:57:48 -08:00
|
|
|
*
|
2018-12-20 17:23:25 -08:00
|
|
|
* @param directiveIndex The index of the directive in LView
|
|
|
|
* @param directiveDef The definition containing the hooks to setup in tView
|
2018-01-23 10:57:48 -08:00
|
|
|
* @param tView The current TView
|
2019-03-01 14:39:28 +01:00
|
|
|
* @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
|
2018-01-23 10:57:48 -08:00
|
|
|
*/
|
2018-12-20 17:23:25 -08:00
|
|
|
export function registerPreOrderHooks(
|
2019-03-01 14:39:28 +01:00
|
|
|
directiveIndex: number, directiveDef: DirectiveDef<any>, tView: TView, nodeIndex: number,
|
|
|
|
initialPreOrderHooksLength: number, initialPreOrderCheckHooksLength: number): void {
|
2019-11-01 13:06:17 -07:00
|
|
|
ngDevMode && assertFirstCreatePass(tView);
|
2019-01-18 17:38:39 +01:00
|
|
|
const {onChanges, onInit, doCheck} = directiveDef;
|
2019-03-01 14:39:28 +01:00
|
|
|
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);
|
|
|
|
}
|
2019-01-18 17:38:39 +01:00
|
|
|
|
|
|
|
if (onChanges) {
|
2019-03-01 14:39:28 +01:00
|
|
|
(tView.preOrderHooks || (tView.preOrderHooks = [])).push(directiveIndex, onChanges);
|
|
|
|
(tView.preOrderCheckHooks || (tView.preOrderCheckHooks = [])).push(directiveIndex, onChanges);
|
2019-01-18 17:38:39 +01:00
|
|
|
}
|
2018-12-20 17:23:25 -08:00
|
|
|
|
2018-03-21 15:10:34 -07:00
|
|
|
if (onInit) {
|
2019-03-01 14:39:28 +01:00
|
|
|
(tView.preOrderHooks || (tView.preOrderHooks = [])).push(-directiveIndex, onInit);
|
2018-03-21 15:10:34 -07:00
|
|
|
}
|
2018-01-23 10:57:48 -08:00
|
|
|
|
2018-03-21 15:10:34 -07:00
|
|
|
if (doCheck) {
|
2019-03-01 14:39:28 +01:00
|
|
|
(tView.preOrderHooks || (tView.preOrderHooks = [])).push(directiveIndex, doCheck);
|
|
|
|
(tView.preOrderCheckHooks || (tView.preOrderCheckHooks = [])).push(directiveIndex, doCheck);
|
2018-01-23 10:57:48 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-12-20 17:23:25 -08:00
|
|
|
*
|
|
|
|
* Loops through the directives on the provided `tNode` and queues hooks to be
|
|
|
|
* run that are not initialization hooks.
|
|
|
|
*
|
|
|
|
* Should be executed during `elementEnd()` and similar to
|
|
|
|
* preserve hook execution order. Content, view, and destroy hooks for projected
|
|
|
|
* components and directives must be called *before* their hosts.
|
|
|
|
*
|
2019-03-01 14:39:28 +01:00
|
|
|
* Sets up the content, view, and destroy hooks on the provided `tView`,
|
|
|
|
* see {@link HookData} for details about the data structure.
|
2018-12-20 17:23:25 -08:00
|
|
|
*
|
|
|
|
* NOTE: This does not set up `onChanges`, `onInit` or `doCheck`, those are set up
|
|
|
|
* separately at `elementStart`.
|
|
|
|
*
|
|
|
|
* @param tView The current TView
|
|
|
|
* @param tNode The TNode whose directives are to be searched for hooks to queue
|
2018-01-22 17:43:52 -08:00
|
|
|
*/
|
2018-12-20 17:23:25 -08:00
|
|
|
export function registerPostOrderHooks(tView: TView, tNode: TNode): void {
|
2019-11-01 13:06:17 -07:00
|
|
|
ngDevMode && assertFirstCreatePass(tView);
|
2019-08-27 16:15:04 +02:00
|
|
|
// It's necessary to loop through the directives at elementEnd() (rather than processing in
|
|
|
|
// directiveCreate) so we can preserve the current hook order. Content, view, and destroy
|
|
|
|
// hooks for projected components and directives must be called *before* their hosts.
|
|
|
|
for (let i = tNode.directiveStart, end = tNode.directiveEnd; i < end; i++) {
|
|
|
|
const directiveDef = tView.data[i] as DirectiveDef<any>;
|
|
|
|
if (directiveDef.afterContentInit) {
|
|
|
|
(tView.contentHooks || (tView.contentHooks = [])).push(-i, directiveDef.afterContentInit);
|
|
|
|
}
|
2018-01-22 17:43:52 -08:00
|
|
|
|
2019-08-27 16:15:04 +02:00
|
|
|
if (directiveDef.afterContentChecked) {
|
|
|
|
(tView.contentHooks || (tView.contentHooks = [])).push(i, directiveDef.afterContentChecked);
|
|
|
|
(tView.contentCheckHooks || (tView.contentCheckHooks = [
|
|
|
|
])).push(i, directiveDef.afterContentChecked);
|
|
|
|
}
|
2018-01-22 19:19:47 -08:00
|
|
|
|
2019-08-27 16:15:04 +02:00
|
|
|
if (directiveDef.afterViewInit) {
|
|
|
|
(tView.viewHooks || (tView.viewHooks = [])).push(-i, directiveDef.afterViewInit);
|
|
|
|
}
|
2018-01-23 10:57:48 -08:00
|
|
|
|
2019-08-27 16:15:04 +02:00
|
|
|
if (directiveDef.afterViewChecked) {
|
|
|
|
(tView.viewHooks || (tView.viewHooks = [])).push(i, directiveDef.afterViewChecked);
|
|
|
|
(tView.viewCheckHooks || (tView.viewCheckHooks = [])).push(i, directiveDef.afterViewChecked);
|
|
|
|
}
|
2018-01-22 19:19:47 -08:00
|
|
|
|
2019-08-27 16:15:04 +02:00
|
|
|
if (directiveDef.onDestroy != null) {
|
|
|
|
(tView.destroyHooks || (tView.destroyHooks = [])).push(i, directiveDef.onDestroy);
|
2018-12-20 17:23:25 -08:00
|
|
|
}
|
2018-01-23 10:57:48 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-01 14:39:28 +01:00
|
|
|
/**
|
|
|
|
* 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].
|
|
|
|
*
|
2019-03-26 13:01:29 -07:00
|
|
|
* 2. Pre-order hooks can be executed in batches, because of the select instruction.
|
2019-03-01 14:39:28 +01:00
|
|
|
* 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].
|
|
|
|
*/
|
|
|
|
|
2019-08-14 14:35:36 +02:00
|
|
|
|
2018-01-22 17:43:52 -08:00
|
|
|
/**
|
2019-08-14 14:35:36 +02:00
|
|
|
* Executes pre-order check hooks ( OnChanges, DoChanges) given a view where all the init hooks were
|
|
|
|
* executed once. This is a light version of executeInitAndCheckPreOrderHooks where we can skip read
|
|
|
|
* / write of the init-hooks related flags.
|
|
|
|
* @param lView The LView where hooks are defined
|
|
|
|
* @param hooks Hooks to be run
|
|
|
|
* @param nodeIndex 3 cases depending on 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)
|
2019-03-01 14:39:28 +01:00
|
|
|
* - number: execute hooks only from the saved index until that node index exclusive (pre-order
|
2019-03-26 13:01:29 -07:00
|
|
|
* case, when executing select(number))
|
2018-01-22 17:43:52 -08:00
|
|
|
*/
|
2019-08-14 14:35:36 +02:00
|
|
|
export function executeCheckHooks(lView: LView, hooks: HookData, nodeIndex?: number | null) {
|
|
|
|
callHooks(lView, hooks, InitPhaseState.InitPhaseCompleted, nodeIndex);
|
2018-01-22 17:43:52 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2019-08-14 14:35:36 +02:00
|
|
|
* Executes post-order init and check hooks (one of AfterContentInit, AfterContentChecked,
|
|
|
|
* AfterViewInit, AfterViewChecked) given a view where there are pending init hooks to be executed.
|
|
|
|
* @param lView The LView where hooks are defined
|
|
|
|
* @param hooks Hooks to be run
|
|
|
|
* @param initPhase A phase for which hooks should be run
|
|
|
|
* @param nodeIndex 3 cases depending on the value:
|
2019-03-01 14:39:28 +01:00
|
|
|
* - 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
|
2019-03-26 13:01:29 -07:00
|
|
|
* case, when executing select(number))
|
2018-01-22 17:43:52 -08:00
|
|
|
*/
|
2019-08-14 14:35:36 +02:00
|
|
|
export function executeInitAndCheckHooks(
|
|
|
|
lView: LView, hooks: HookData, initPhase: InitPhaseState, nodeIndex?: number | null) {
|
|
|
|
ngDevMode && assertNotEqual(
|
|
|
|
initPhase, InitPhaseState.InitPhaseCompleted,
|
|
|
|
'Init pre-order hooks should not be called more than once');
|
|
|
|
if ((lView[FLAGS] & LViewFlags.InitPhaseStateMask) === initPhase) {
|
|
|
|
callHooks(lView, hooks, initPhase, nodeIndex);
|
2019-01-18 17:38:39 +01:00
|
|
|
}
|
2019-08-14 14:35:36 +02:00
|
|
|
}
|
2019-07-26 11:52:15 +02:00
|
|
|
|
2019-08-14 14:35:36 +02:00
|
|
|
export function incrementInitPhaseFlags(lView: LView, initPhase: InitPhaseState): void {
|
|
|
|
ngDevMode &&
|
|
|
|
assertNotEqual(
|
|
|
|
initPhase, InitPhaseState.InitPhaseCompleted,
|
|
|
|
'Init hooks phase should not be incremented after all init hooks have been run.');
|
|
|
|
let flags = lView[FLAGS];
|
|
|
|
if ((flags & LViewFlags.InitPhaseStateMask) === initPhase) {
|
2019-07-26 11:52:15 +02:00
|
|
|
flags &= LViewFlags.IndexWithinInitPhaseReset;
|
|
|
|
flags += LViewFlags.InitPhaseStateIncrementer;
|
2019-08-14 14:35:36 +02:00
|
|
|
lView[FLAGS] = flags;
|
2018-01-22 17:43:52 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-01-22 19:19:47 -08:00
|
|
|
* Calls lifecycle hooks with their contexts, skipping init hooks if it's not
|
2019-01-14 17:39:21 -08:00
|
|
|
* the first LView pass
|
2018-01-22 17:43:52 -08:00
|
|
|
*
|
2018-01-22 19:19:47 -08:00
|
|
|
* @param currentView The current view
|
2018-01-22 17:43:52 -08:00
|
|
|
* @param arr The array in which the hooks are found
|
2019-03-01 14:39:28 +01:00
|
|
|
* @param initPhaseState the current state of the init phase
|
2019-08-14 14:35:36 +02:00
|
|
|
* @param currentNodeIndex 3 cases depending on the value:
|
2019-03-01 14:39:28 +01:00
|
|
|
* - 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
|
2019-03-26 13:01:29 -07:00
|
|
|
* case, when executing select(number))
|
2018-01-22 17:43:52 -08:00
|
|
|
*/
|
2019-03-01 14:39:28 +01:00
|
|
|
function callHooks(
|
|
|
|
currentView: LView, arr: HookData, initPhase: InitPhaseState,
|
|
|
|
currentNodeIndex: number | null | undefined): void {
|
2019-08-14 14:35:36 +02:00
|
|
|
ngDevMode && assertEqual(
|
|
|
|
getCheckNoChangesMode(), false,
|
|
|
|
'Hooks should never be run in the check no changes mode.');
|
2019-03-01 14:39:28 +01:00
|
|
|
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++) {
|
2019-01-18 17:38:39 +01:00
|
|
|
const hook = arr[i + 1] as() => void;
|
2019-03-01 14:39:28 +01:00
|
|
|
if (typeof hook === 'number') {
|
|
|
|
lastNodeIndexFound = arr[i] as number;
|
|
|
|
if (currentNodeIndex != null && lastNodeIndexFound >= currentNodeIndex) {
|
|
|
|
break;
|
2019-01-18 17:38:39 +01:00
|
|
|
}
|
|
|
|
} else {
|
2019-03-01 14:39:28 +01:00
|
|
|
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;
|
2019-01-18 17:38:39 +01:00
|
|
|
hook.call(directive);
|
|
|
|
}
|
2019-03-01 14:39:28 +01:00
|
|
|
} else {
|
|
|
|
hook.call(directive);
|
2018-01-22 17:43:52 -08:00
|
|
|
}
|
|
|
|
}
|