diff --git a/packages/core/src/render3/hooks.ts b/packages/core/src/render3/hooks.ts index 15590d7243..37de184889 100644 --- a/packages/core/src/render3/hooks.ts +++ b/packages/core/src/render3/hooks.ts @@ -1,18 +1,15 @@ -import {LifecycleHooksMap} from './interfaces/definition'; +import {DirectiveDef, LifecycleHooksMap} from './interfaces/definition'; import {LNodeFlags} from './interfaces/node'; import {HookData, LView, TView} from './interfaces/view'; + /** * Enum used by the lifecycle (l) instruction to determine which lifecycle hook is requesting * processing. */ -export const enum LifecycleHook { - ON_INIT = 0b00, - ON_CHECK = 0b01, - ON_CHANGES = 0b10 -} +export const enum LifecycleHook {ON_INIT = 0b00, ON_CHECK = 0b01, ON_CHANGES = 0b10} /** Constants used by lifecycle hooks to determine when and how a hook should be called. */ export const enum LifecycleHookUtils { @@ -24,34 +21,67 @@ export const enum LifecycleHookUtils { } /** - * Loops through the directives on a node and queues their afterContentInit, - * afterContentChecked, and onDestroy hooks, if they exist. + * Loops through the directives on a node and queues their all hooks except ngOnInit + * and ngDoCheck, which are queued separately in E. */ export function queueLifecycleHooks(flags: number, currentView: LView): void { - // It's necessary to loop through the directives at elementEnd() (rather than storing - // the hooks at creation time) so we can preserve the current hook order. All hooks - // for projected components and directives must be called *before* their hosts. + const tView = currentView.tView; + if (tView.firstTemplatePass === true) { + const size = (flags & LNodeFlags.SIZE_MASK) >> LNodeFlags.SIZE_SHIFT; + const start = flags >> LNodeFlags.INDX_SHIFT; + + // It's necessary to loop through the directives at elementEnd() (rather than storing + // the hooks at creation time) so we can preserve the current hook order. All hooks + // for projected components and directives must be called *before* their hosts. + for (let i = start, end = start + size; i < end; i++) { + const hooks = (tView.data[i] as DirectiveDef).lifecycleHooks; + queueContentHooks(hooks, tView, i); + queueViewHooks(hooks, tView, i); + } + } + const size = (flags & LNodeFlags.SIZE_MASK) >> LNodeFlags.SIZE_SHIFT; const start = flags >> LNodeFlags.INDX_SHIFT; - let contentHooks = currentView.contentHooks; let cleanup = currentView.cleanup; for (let i = start, end = start + size; i < end; i++) { const instance = currentView.data[i]; - if (instance.ngAfterContentInit != null) { - (contentHooks || (currentView.contentHooks = contentHooks = [ - ])).push(LifecycleHook.ON_INIT, instance.ngAfterContentInit, instance); - } - if (instance.ngAfterContentChecked != null) { - (contentHooks || (currentView.contentHooks = contentHooks = [ - ])).push(LifecycleHook.ON_CHECK, instance.ngAfterContentChecked, instance); - } if (instance.ngOnDestroy != null) { (cleanup || (currentView.cleanup = cleanup = [])).push(instance.ngOnDestroy, instance); } } } +function queueContentHooks(hooks: LifecycleHooksMap, tView: TView, i: number): void { + if (hooks.afterContentInit != null) { + (tView.contentHooks || (tView.contentHooks = [])).push(getInitFlags(i), hooks.afterContentInit); + } + + if (hooks.afterContentChecked != null) { + (tView.contentHooks || (tView.contentHooks = [ + ])).push(getCheckFlags(i), hooks.afterContentChecked); + } +} +function queueViewHooks(hooks: LifecycleHooksMap, tView: TView, i: number): void { + if (hooks.afterViewInit != null) { + (tView.viewHooks || (tView.viewHooks = [])).push(getInitFlags(i), hooks.afterViewInit); + } + + if (hooks.afterViewChecked != null) { + (tView.viewHooks || (tView.viewHooks = [])).push(getCheckFlags(i), hooks.afterViewChecked); + } +} + +/** Generates flags for init-only hooks */ +function getInitFlags(index: number): number { + return index << LifecycleHookUtils.INDX_SHIFT; +} + +/** Generates flags for hooks called every change detection run */ +function getCheckFlags(index: number): number { + return (index << LifecycleHookUtils.INDX_SHIFT) | LifecycleHook.ON_CHECK; +} + /** * If this is the first template pass, any ngOnInit or ngDoCheck hooks on the current directive * will be queued on TView.initHooks. @@ -65,12 +95,12 @@ export function queueLifecycleHooks(flags: number, currentView: LView): void { * @param tView The current TView */ export function queueInitHooks(index: number, hooks: LifecycleHooksMap, tView: TView): void { - if (tView.firstTemplatePass && hooks.onInit != null) { + if (tView.firstTemplatePass === true && hooks.onInit != null) { const hookFlags = index << LifecycleHookUtils.INDX_SHIFT; (tView.initHooks || (tView.initHooks = [])).push(hookFlags, hooks.onInit); } - if (tView.firstTemplatePass && hooks.doCheck != null) { + if (tView.firstTemplatePass === true && hooks.doCheck != null) { const hookFlags = (index << LifecycleHookUtils.INDX_SHIFT) | LifecycleHook.ON_CHECK; (tView.initHooks || (tView.initHooks = [])).push(hookFlags, hooks.doCheck); } @@ -86,26 +116,18 @@ export function executeInitHooks(currentView: LView): void { const initHooks = currentView.tView.initHooks; if (currentView.initHooksCalled === false && initHooks != null) { - const data = currentView.data; - const creationMode = currentView.creationMode; - - for (let i = 0; i < initHooks.length; i += 2) { - const flags = initHooks[i] as number; - const hook = initHooks[i | 1] as() => void; - const onInit = (flags & LifecycleHookUtils.TYPE_MASK) === LifecycleHook.ON_INIT; - const instance = data[flags >> LifecycleHookUtils.INDX_SHIFT]; - if (onInit === false || creationMode) { - hook.call(instance); - } - } + executeLifecycleHooks(currentView, initHooks); currentView.initHooksCalled = true; } } /** Iterates over view hook functions and calls them. */ -export function executeViewHooks(data: any[], viewHookStartIndex: number | null): void { - if (viewHookStartIndex == null) return; - executeHooksAndRemoveInits(data, viewHookStartIndex); +export function executeViewHooks(currentView: LView): void { + const viewHooks = currentView.tView.viewHooks; + + if (viewHooks != null) { + executeLifecycleHooks(currentView, viewHooks); + } } /** @@ -113,41 +135,32 @@ export function executeViewHooks(data: any[], viewHookStartIndex: number | null) * out afterContentInit hooks to prep for the next run in update mode. */ export function executeContentHooks(currentView: LView): void { - if (currentView.contentHooks != null && currentView.contentHooksCalled === false) { - executeHooksAndRemoveInits(currentView.contentHooks, 0); + const contentHooks = currentView.tView.contentHooks; + + if (currentView.contentHooksCalled === false && contentHooks != null) { + executeLifecycleHooks(currentView, contentHooks); currentView.contentHooksCalled = true; } } /** - * Calls lifecycle hooks with their contexts, then splices out any init-only hooks - * to prep for the next run in update mode. + * Calls lifecycle hooks with their contexts, skipping init hooks if it's not + * creation mode. * + * @param currentView The current view * @param arr The array in which the hooks are found - * @param startIndex The index at which to start calling hooks */ -function executeHooksAndRemoveInits(arr: any[], startIndex: number): void { - // Instead of using splice to remove init hooks after their first run (expensive), we - // shift over the AFTER_CHECKED hooks as we call them and truncate once at the end. - let checkIndex = startIndex; - let writeIndex = startIndex; - while (checkIndex < arr.length) { - // Call lifecycle hook with its context - arr[checkIndex + 1].call(arr[checkIndex + 2]); +function executeLifecycleHooks(currentView: LView, arr: HookData): void { + const data = currentView.data; + const creationMode = currentView.creationMode; - if (arr[checkIndex] === LifecycleHook.ON_CHECK) { - // We know if the writeIndex falls behind that there is an init that needs to - // be overwritten. - if (writeIndex < checkIndex) { - arr[writeIndex] = arr[checkIndex]; - arr[writeIndex + 1] = arr[checkIndex + 1]; - arr[writeIndex + 2] = arr[checkIndex + 2]; - } - writeIndex += 3; + for (let i = 0; i < arr.length; i += 2) { + const flags = arr[i] as number; + const hook = arr[i | 1] as() => void; + const initOnly = (flags & LifecycleHookUtils.TYPE_MASK) === LifecycleHook.ON_INIT; + const instance = data[flags >> LifecycleHookUtils.INDX_SHIFT]; + if (initOnly === false || creationMode) { + hook.call(instance); } - checkIndex += 3; } - - // Truncate once at the writeIndex - arr.length = writeIndex; } diff --git a/packages/core/src/render3/index.ts b/packages/core/src/render3/index.ts index 6cd9ec6593..af03107749 100644 --- a/packages/core/src/render3/index.ts +++ b/packages/core/src/render3/index.ts @@ -50,7 +50,6 @@ export { elementStart as E, elementStyle as s, - lifecycle as l, listener as L, memory as m, diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 639a07f312..22e0647d3c 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -27,7 +27,7 @@ import {isNodeMatchingSelector} from './node_selector_matcher'; import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveType} from './interfaces/definition'; import {RComment, RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, ObjectOrientedRenderer3, RendererStyleFlags3} from './interfaces/renderer'; import {isDifferent, stringify} from './util'; -import {LifecycleHook, executeViewHooks, executeContentHooks, queueLifecycleHooks, queueInitHooks, executeInitHooks} from './hooks'; +import {executeViewHooks, executeContentHooks, queueLifecycleHooks, queueInitHooks, executeInitHooks} from './hooks'; /** @@ -118,9 +118,6 @@ let bindingIndex: number; */ let cleanup: any[]|null; -/** Index in the data array at which view hooks begin to be stored. */ -let viewHookStartIndex: number|null; - /** * Swap the current state with a new state. * @@ -140,7 +137,6 @@ export function enterView(newView: LView, host: LElementNode | LViewNode | null) tData = newView.tView.data; creationMode = newView.creationMode; - viewHookStartIndex = newView.viewHookStartIndex; cleanup = newView.cleanup; renderer = newView.renderer; @@ -158,7 +154,8 @@ export function enterView(newView: LView, host: LElementNode | LViewNode | null) * the direction of traversal (up or down the view tree) a bit clearer. */ export function leaveView(newView: LView): void { - executeViewHooks(data, viewHookStartIndex); + executeViewHooks(currentView); + currentView.creationMode = false; currentView.initHooksCalled = false; currentView.contentHooksCalled = false; if (currentView.tView.firstTemplatePass) currentView.tView.firstTemplatePass = false; @@ -175,14 +172,12 @@ export function createLView( data: [], tView: tView, cleanup: null, - contentHooks: null, renderer: renderer, child: null, tail: null, next: null, bindingStartIndex: null, creationMode: true, - viewHookStartIndex: null, template: template, context: context, dynamicViewCount: 0, @@ -349,7 +344,6 @@ export function renderComponentOrTemplate( if (rendererFactory.end) { rendererFactory.end(); } - hostView.creationMode = false; leaveView(oldView); } } @@ -490,7 +484,7 @@ function getOrCreateTView(template: ComponentTemplate): TView { } export function createTView(): TView { - return {data: [], firstTemplatePass: true, initHooks: null}; + return {data: [], firstTemplatePass: true, initHooks: null, contentHooks: null, viewHooks: null}; } function setUpAttributes(native: RElement, attrs: string[]): void { @@ -968,25 +962,6 @@ function generateInitialInputs( return initialInputData; } -/** - * Accepts a lifecycle hook type and determines when and how the related lifecycle hook - * callback should run. - * - * @param lifecycle - * @param self - * @param method - */ -export function lifecycle(lifecycle: LifecycleHook, self: any, method: Function): boolean { - if (creationMode && - (lifecycle === LifecycleHook.ON_INIT || lifecycle === LifecycleHook.ON_CHECK)) { - if (viewHookStartIndex == null) { - currentView.viewHookStartIndex = viewHookStartIndex = data.length; - } - data.push(lifecycle, method, self); - } - return false; -} - ////////////////////////// //// ViewContainer & View @@ -1170,7 +1145,6 @@ export function viewEnd(): void { if (viewIdChanged) { insertView(container, viewNode, containerState.nextIndex - 1); - currentView.creationMode = false; } } leaveView(currentView !.parent !); @@ -1207,7 +1181,6 @@ export const componentRefresh: try { template(directive, creationMode); } finally { - hostView.creationMode = false; refreshDynamicChildren(); leaveView(oldView); } diff --git a/packages/core/src/render3/interfaces/view.ts b/packages/core/src/render3/interfaces/view.ts index 8dd09dd476..87c4226d6d 100644 --- a/packages/core/src/render3/interfaces/view.ts +++ b/packages/core/src/render3/interfaces/view.ts @@ -33,9 +33,6 @@ export interface LView { */ creationMode: boolean; - /** The index in the data array at which view hooks begin to be stored. */ - viewHookStartIndex: number|null; - /** * The parent view is needed when we exit the view and must restore the previous * `LView`. Without this, the render method would have to keep a stack of @@ -90,19 +87,6 @@ export interface LView { */ cleanup: any[]|null; - /** - * Array of ngAfterContentInit and ngAfterContentChecked hooks. - * - * These need to be queued so they can be called all at once after init hooks - * and any embedded views are finished processing (to maintain backwards-compatible - * order). - * - * 1st index is: type of hook (afterContentInit or afterContentChecked) - * 2nd index is: method to call - * 3rd index is: context - */ - contentHooks: any[]|null; - /** * Whether or not the ngOnInit and ngDoCheck hooks have been called in this change * detection run. @@ -212,12 +196,30 @@ export interface TView { firstTemplatePass: boolean; /** - * Array of init hooks that should be executed for this view. + * Array of ngOnInit and ngDoCheck hooks that should be executed for this view. * * Even indices: Flags (1st bit: hook type, remaining: directive index) * Odd indices: Hook function */ initHooks: HookData|null; + + /** + * Array of ngAfterContentInit and ngAfterContentChecked hooks that should be executed for + * this view. + * + * Even indices: Flags (1st bit: hook type, remaining: directive index) + * Odd indices: Hook function + */ + contentHooks: HookData|null; + + /** + * Array of ngAfterViewInit and ngAfterViewChecked hooks that should be executed for + * this view. + * + * Even indices: Flags (1st bit: hook type, remaining: directive index) + * Odd indices: Hook function + */ + viewHooks: HookData|null; } /** diff --git a/packages/core/test/render3/lifecycle_spec.ts b/packages/core/test/render3/lifecycle_spec.ts index e688beaef2..a628641511 100644 --- a/packages/core/test/render3/lifecycle_spec.ts +++ b/packages/core/test/render3/lifecycle_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ -import {C, ComponentDef, ComponentTemplate, E, L, LifecycleHook, P, T, V, b, cR, cr, defineComponent, defineDirective, e, l, m, p, pD, r, v} from '../../src/render3/index'; +import {C, ComponentTemplate, E, L, P, T, V, b, cR, cr, defineComponent, defineDirective, e, m, p, pD, r, v} from '../../src/render3/index'; import {containerEl, renderToHtml} from './render_util'; @@ -840,12 +840,6 @@ describe('lifecycles', () => { type: Component, tag: name, factory: () => new Component(), - refresh: (directiveIndex: number, elementIndex: number) => { - r(directiveIndex, elementIndex, template); - const comp = m(directiveIndex) as Component; - l(LifecycleHook.ON_INIT, comp, comp.ngAfterViewInit); - l(LifecycleHook.ON_CHECK, comp, comp.ngAfterViewChecked); - }, inputs: {val: 'val'}, template: template }); @@ -961,7 +955,10 @@ describe('lifecycles', () => { function Template(ctx: any, cm: boolean) { if (cm) { E(0, Comp); - { E(2, ProjectedComp); } + { + E(2, ProjectedComp); + e(); + } e(); } Comp.ngComponentDef.h(1, 0); @@ -986,10 +983,16 @@ describe('lifecycles', () => { function Template(ctx: any, cm: boolean) { if (cm) { E(0, Comp); - { E(2, ProjectedComp); } + { + E(2, ProjectedComp); + e(); + } e(); E(4, Comp); - { E(6, ProjectedComp); } + { + E(6, ProjectedComp); + e(); + } e(); } p(0, 'val', 1); @@ -1019,7 +1022,10 @@ describe('lifecycles', () => { const ParentComp = createAfterViewInitComponent('parent', (ctx: any, cm: boolean) => { if (cm) { E(0, Comp); - { E(2, ProjectedComp); } + { + E(2, ProjectedComp); + e(); + } e(); } p(0, 'val', b(ctx.val)); @@ -1707,12 +1713,6 @@ describe('lifecycles', () => { type: Component, tag: name, factory: () => new Component(), - refresh: function(directiveIndex: number, elementIndex: number): void { - r(directiveIndex, elementIndex, template); - const comp = m(directiveIndex) as Component; - l(LifecycleHook.ON_INIT, comp, comp.ngAfterViewInit); - l(LifecycleHook.ON_CHECK, comp, comp.ngAfterViewChecked); - }, inputs: {val: 'val'}, template }); }; diff --git a/packages/core/test/render3/outputs_spec.ts b/packages/core/test/render3/outputs_spec.ts index e43bde89fc..f10eacc7ee 100644 --- a/packages/core/test/render3/outputs_spec.ts +++ b/packages/core/test/render3/outputs_spec.ts @@ -8,7 +8,7 @@ import {EventEmitter} from '@angular/core'; -import {C, E, L, LifecycleHook, T, V, b, cR, cr, defineComponent, defineDirective, e, l, p, v} from '../../src/render3/index'; +import {C, E, L, T, V, b, cR, cr, defineComponent, defineDirective, e, p, v} from '../../src/render3/index'; import {containerEl, renderToHtml} from './render_util';