diff --git a/integration/_payload-limits.json b/integration/_payload-limits.json index 5cb49533a6..5308faa26c 100644 --- a/integration/_payload-limits.json +++ b/integration/_payload-limits.json @@ -12,7 +12,7 @@ "master": { "uncompressed": { "runtime": 1440, - "main": 13164, + "main": 13411, "polyfills": 45340 } } diff --git a/packages/core/src/render3/hooks.ts b/packages/core/src/render3/hooks.ts index 35678c7d68..8f7c1a4945 100644 --- a/packages/core/src/render3/hooks.ts +++ b/packages/core/src/render3/hooks.ts @@ -6,11 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ -import {assertEqual} from '../util/assert'; +import {assertEqual, assertNotEqual} from '../util/assert'; import {DirectiveDef} from './interfaces/definition'; import {TNode} from './interfaces/node'; import {FLAGS, HookData, InitPhaseState, LView, LViewFlags, PREORDER_HOOK_FLAGS, PreOrderHookFlags, TView} from './interfaces/view'; +import {getCheckNoChangesMode} from './state'; @@ -138,70 +139,57 @@ export function registerPostOrderHooks(tView: TView, tNode: TNode): void { * 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`. - * - * @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 select(number)) - */ -export function executePreOrderHooks( - currentView: LView, tView: TView, checkNoChangesMode: boolean, - currentNodeIndex: number | undefined): void { - if (!checkNoChangesMode) { - executeHooks( - currentView, tView.preOrderHooks, tView.preOrderCheckHooks, checkNoChangesMode, - InitPhaseState.OnInitHooksToBeRun, - currentNodeIndex !== undefined ? currentNodeIndex : null); - } -} /** - * Executes hooks against the given `LView` based off of whether or not - * This is the first pass. - * - * @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: + * 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) * - number: execute hooks only from the saved index until that node index exclusive (pre-order * case, when executing select(number)) */ -export function executeHooks( - currentView: LView, firstPassHooks: HookData | null, checkHooks: HookData | null, - checkNoChangesMode: boolean, initPhaseState: InitPhaseState, - currentNodeIndex: number | null | undefined): void { - if (checkNoChangesMode) return; +export function executeCheckHooks(lView: LView, hooks: HookData, nodeIndex?: number | null) { + callHooks(lView, hooks, InitPhaseState.InitPhaseCompleted, nodeIndex); +} - if (checkHooks !== null || firstPassHooks !== null) { - const hooksToCall = (currentView[FLAGS] & LViewFlags.InitPhaseStateMask) === initPhaseState ? - firstPassHooks : - checkHooks; - if (hooksToCall !== null) { - callHooks(currentView, hooksToCall, initPhaseState, currentNodeIndex); - } +/** + * 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: + * - 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 select(number)) + */ +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); } +} - // The init phase state must be always checked here as it may have been recursively updated - let flags = currentView[FLAGS]; - if (currentNodeIndex == null && (flags & LViewFlags.InitPhaseStateMask) === initPhaseState && - initPhaseState !== InitPhaseState.InitPhaseCompleted) { +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) { flags &= LViewFlags.IndexWithinInitPhaseReset; flags += LViewFlags.InitPhaseStateIncrementer; - currentView[FLAGS] = flags; + lView[FLAGS] = flags; } } @@ -212,7 +200,7 @@ 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: + * @param currentNodeIndex 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) @@ -222,6 +210,9 @@ export function executeHooks( function callHooks( currentView: LView, arr: HookData, initPhase: InitPhaseState, currentNodeIndex: number | null | undefined): void { + ngDevMode && assertEqual( + getCheckNoChangesMode(), false, + 'Hooks should never be run in the check no changes mode.'); const startIndex = currentNodeIndex !== undefined ? (currentView[PREORDER_HOOK_FLAGS] & PreOrderHookFlags.IndexOfTheNextPreOrderHookMaskMask) : 0; diff --git a/packages/core/src/render3/instructions/container.ts b/packages/core/src/render3/instructions/container.ts index dbf84f8abc..f2e25ac44d 100644 --- a/packages/core/src/render3/instructions/container.ts +++ b/packages/core/src/render3/instructions/container.ts @@ -8,11 +8,11 @@ import {assertDataInRange, assertEqual} from '../../util/assert'; import {assertHasParent} from '../assert'; import {attachPatchData} from '../context_discovery'; -import {executePreOrderHooks, registerPostOrderHooks} from '../hooks'; +import {executeCheckHooks, executeInitAndCheckHooks, incrementInitPhaseFlags, registerPostOrderHooks} from '../hooks'; import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer} from '../interfaces/container'; import {ComponentTemplate} from '../interfaces/definition'; import {LocalRefExtractor, TAttributes, TContainerNode, TNode, TNodeType, TViewNode} from '../interfaces/node'; -import {BINDING_INDEX, HEADER_OFFSET, LView, RENDERER, TVIEW, T_HOST} from '../interfaces/view'; +import {BINDING_INDEX, FLAGS, HEADER_OFFSET, InitPhaseState, LView, LViewFlags, RENDERER, TVIEW, T_HOST} from '../interfaces/view'; import {assertNodeType} from '../node_assert'; import {appendChild, removeView} from '../node_manipulation'; import {getCheckNoChangesMode, getIsParent, getLView, getPreviousOrParentTNode, setIsNotParent, setPreviousOrParentTNode} from '../state'; @@ -112,7 +112,22 @@ 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). - executePreOrderHooks(lView, tView, getCheckNoChangesMode(), undefined); + if (!getCheckNoChangesMode()) { + const hooksInitPhaseCompleted = + (lView[FLAGS] & LViewFlags.InitPhaseStateMask) === InitPhaseState.InitPhaseCompleted; + if (hooksInitPhaseCompleted) { + const preOrderCheckHooks = tView.preOrderCheckHooks; + if (preOrderCheckHooks !== null) { + executeCheckHooks(lView, preOrderCheckHooks, null); + } + } else { + const preOrderHooks = tView.preOrderHooks; + if (preOrderHooks !== null) { + executeInitAndCheckHooks(lView, preOrderHooks, InitPhaseState.OnInitHooksToBeRun, null); + } + incrementInitPhaseFlags(lView, InitPhaseState.OnInitHooksToBeRun); + } + } } /** diff --git a/packages/core/src/render3/instructions/select.ts b/packages/core/src/render3/instructions/select.ts index e10b782fad..41f63ae874 100644 --- a/packages/core/src/render3/instructions/select.ts +++ b/packages/core/src/render3/instructions/select.ts @@ -6,11 +6,12 @@ * found in the LICENSE file at https://angular.io/license */ import {assertDataInRange, assertGreaterThan} from '../../util/assert'; -import {executePreOrderHooks} from '../hooks'; -import {HEADER_OFFSET, LView, TVIEW} from '../interfaces/view'; +import {executeCheckHooks, executeInitAndCheckHooks} from '../hooks'; +import {FLAGS, HEADER_OFFSET, InitPhaseState, LView, LViewFlags, TVIEW} from '../interfaces/view'; import {getCheckNoChangesMode, getLView, setSelectedIndex} from '../state'; + /** * Selects an element for later binding instructions. * @@ -41,7 +42,22 @@ export function selectInternal(lView: LView, index: number, checkNoChangesMode: ngDevMode && assertDataInRange(lView, index + HEADER_OFFSET); // Flush the initial hooks for elements in the view that have been added up to this point. - executePreOrderHooks(lView, lView[TVIEW], checkNoChangesMode, index); + // PERF WARNING: do NOT extract this to a separate function without running benchmarks + if (!checkNoChangesMode) { + const hooksInitPhaseCompleted = + (lView[FLAGS] & LViewFlags.InitPhaseStateMask) === InitPhaseState.InitPhaseCompleted; + if (hooksInitPhaseCompleted) { + const preOrderCheckHooks = lView[TVIEW].preOrderCheckHooks; + if (preOrderCheckHooks !== null) { + executeCheckHooks(lView, preOrderCheckHooks, index); + } + } else { + const preOrderHooks = lView[TVIEW].preOrderHooks; + if (preOrderHooks !== null) { + executeInitAndCheckHooks(lView, preOrderHooks, InitPhaseState.OnInitHooksToBeRun, index); + } + } + } // We must set the selected index *after* running the hooks, because hooks may have side-effects // that cause other template functions to run, thus updating the selected index, which is global diff --git a/packages/core/src/render3/instructions/shared.ts b/packages/core/src/render3/instructions/shared.ts index 7ba9f5cabf..8052b0ce28 100644 --- a/packages/core/src/render3/instructions/shared.ts +++ b/packages/core/src/render3/instructions/shared.ts @@ -17,7 +17,7 @@ import {assertFirstTemplatePass, assertLView} from '../assert'; import {attachPatchData, getComponentViewByInstance} from '../context_discovery'; import {diPublicInInjector, getNodeInjectable, getOrCreateNodeInjectorForNode} from '../di'; import {throwMultipleComponentError} from '../errors'; -import {executeHooks, executePreOrderHooks, registerPreOrderHooks} from '../hooks'; +import {executeCheckHooks, executeInitAndCheckHooks, incrementInitPhaseFlags, registerPreOrderHooks} from '../hooks'; import {ACTIVE_INDEX, CONTAINER_HEADER_OFFSET, LContainer} from '../interfaces/container'; import {ComponentDef, ComponentTemplate, DirectiveDef, DirectiveDefListOrFactory, FactoryFn, PipeDefListOrFactory, RenderFlags, ViewQueriesFunction} from '../interfaces/definition'; import {INJECTOR_BLOOM_PARENT_SIZE, NodeInjectorFactory} from '../interfaces/injector'; @@ -372,7 +372,8 @@ export function renderView(lView: LView, tView: TView, context: T): void { export function refreshView( lView: LView, tView: TView, templateFn: ComponentTemplate<{}>| null, context: T) { ngDevMode && assertEqual(isCreationMode(lView), false, 'Should be run in update mode'); - let oldView = enterView(lView, lView[T_HOST]); + const oldView = enterView(lView, lView[T_HOST]); + const flags = lView[FLAGS]; try { resetPreOrderHookFlags(lView); @@ -385,8 +386,25 @@ export function refreshView( lView[BINDING_INDEX] = tView.bindingStartIndex; const checkNoChangesMode = getCheckNoChangesMode(); + const hooksInitPhaseCompleted = + (flags & LViewFlags.InitPhaseStateMask) === InitPhaseState.InitPhaseCompleted; - executePreOrderHooks(lView, tView, checkNoChangesMode, undefined); + // execute pre-order hooks (OnInit, OnChanges, DoChanges) + // PERF WARNING: do NOT extract this to a separate function without running benchmarks + if (!checkNoChangesMode) { + if (hooksInitPhaseCompleted) { + const preOrderCheckHooks = tView.preOrderCheckHooks; + if (preOrderCheckHooks !== null) { + executeCheckHooks(lView, preOrderCheckHooks, null); + } + } else { + const preOrderHooks = tView.preOrderHooks; + if (preOrderHooks !== null) { + executeInitAndCheckHooks(lView, preOrderHooks, InitPhaseState.OnInitHooksToBeRun, null); + } + incrementInitPhaseFlags(lView, InitPhaseState.OnInitHooksToBeRun); + } + } refreshDynamicEmbeddedViews(lView); @@ -395,10 +413,23 @@ export function refreshView( refreshContentQueries(tView, lView); } - resetPreOrderHookFlags(lView); - executeHooks( - lView, tView.contentHooks, tView.contentCheckHooks, checkNoChangesMode, - InitPhaseState.AfterContentInitHooksToBeRun, undefined); + // execute content hooks (AfterContentInit, AfterContentChecked) + // PERF WARNING: do NOT extract this to a separate function without running benchmarks + if (!checkNoChangesMode) { + if (hooksInitPhaseCompleted) { + const contentCheckHooks = tView.contentCheckHooks; + if (contentCheckHooks !== null) { + executeCheckHooks(lView, contentCheckHooks); + } + } else { + const contentHooks = tView.contentHooks; + if (contentHooks !== null) { + executeInitAndCheckHooks( + lView, contentHooks, InitPhaseState.AfterContentInitHooksToBeRun); + } + incrementInitPhaseFlags(lView, InitPhaseState.AfterContentInitHooksToBeRun); + } + } setHostBindings(tView, lView); @@ -413,10 +444,22 @@ export function refreshView( refreshChildComponents(lView, components); } - resetPreOrderHookFlags(lView); - executeHooks( - lView, tView.viewHooks, tView.viewCheckHooks, checkNoChangesMode, - InitPhaseState.AfterViewInitHooksToBeRun, undefined); + // execute view hooks (AfterViewInit, AfterViewChecked) + // PERF WARNING: do NOT extract this to a separate function without running benchmarks + if (!checkNoChangesMode) { + if (hooksInitPhaseCompleted) { + const viewCheckHooks = tView.viewCheckHooks; + if (viewCheckHooks !== null) { + executeCheckHooks(lView, viewCheckHooks); + } + } else { + const viewHooks = tView.viewHooks; + if (viewHooks !== null) { + executeInitAndCheckHooks(lView, viewHooks, InitPhaseState.AfterViewInitHooksToBeRun); + } + incrementInitPhaseFlags(lView, InitPhaseState.AfterViewInitHooksToBeRun); + } + } } finally { lView[FLAGS] &= ~(LViewFlags.Dirty | LViewFlags.FirstLViewPass); diff --git a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json index 3c0e5f02f1..266c913290 100644 --- a/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json +++ b/packages/core/test/bundling/cyclic_import/bundle.golden_symbols.json @@ -251,14 +251,14 @@ { "name": "enterView" }, + { + "name": "executeCheckHooks" + }, { "name": "executeContentQueries" }, { - "name": "executeHooks" - }, - { - "name": "executePreOrderHooks" + "name": "executeInitAndCheckHooks" }, { "name": "executeTemplate" @@ -416,6 +416,9 @@ { "name": "incrementActiveDirectiveId" }, + { + "name": "incrementInitPhaseFlags" + }, { "name": "initNodeFlags" }, diff --git a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json index 1490c0f856..6c01d3daba 100644 --- a/packages/core/test/bundling/hello_world/bundle.golden_symbols.json +++ b/packages/core/test/bundling/hello_world/bundle.golden_symbols.json @@ -210,10 +210,10 @@ "name": "enterView" }, { - "name": "executeHooks" + "name": "executeCheckHooks" }, { - "name": "executePreOrderHooks" + "name": "executeInitAndCheckHooks" }, { "name": "executeTemplate" @@ -323,6 +323,9 @@ { "name": "incrementActiveDirectiveId" }, + { + "name": "incrementInitPhaseFlags" + }, { "name": "initNodeFlags" }, diff --git a/packages/core/test/bundling/todo/bundle.golden_symbols.json b/packages/core/test/bundling/todo/bundle.golden_symbols.json index 5cdbbbe518..fe2d63d0a0 100644 --- a/packages/core/test/bundling/todo/bundle.golden_symbols.json +++ b/packages/core/test/bundling/todo/bundle.golden_symbols.json @@ -632,11 +632,14 @@ { "name": "enterView" }, + { + "name": "executeCheckHooks" + }, { "name": "executeContentQueries" }, { - "name": "executeHooks" + "name": "executeInitAndCheckHooks" }, { "name": "executeListenerWithErrorHandling" @@ -644,9 +647,6 @@ { "name": "executeOnDestroys" }, - { - "name": "executePreOrderHooks" - }, { "name": "executeTemplate" }, @@ -938,6 +938,9 @@ { "name": "incrementActiveDirectiveId" }, + { + "name": "incrementInitPhaseFlags" + }, { "name": "initNodeFlags" },