parent
98174758ad
commit
9c99e6a838
|
@ -13,8 +13,7 @@ import {ComponentRef as viewEngine_ComponentRef} from '../linker/component_facto
|
||||||
import {EmbeddedViewRef as viewEngine_EmbeddedViewRef} from '../linker/view_ref';
|
import {EmbeddedViewRef as viewEngine_EmbeddedViewRef} from '../linker/view_ref';
|
||||||
|
|
||||||
import {assertNotNull} from './assert';
|
import {assertNotNull} from './assert';
|
||||||
|
import {NG_HOST_SYMBOL, createError, createLView, createTView, directiveCreate, enterView, hostElement, leaveView, locateHostElement, renderComponentOrTemplate} from './instructions';
|
||||||
import {NG_HOST_SYMBOL, createError, createLView, directiveCreate, enterView, hostElement, leaveView, locateHostElement, renderComponentOrTemplate} from './instructions';
|
|
||||||
import {ComponentDef, ComponentType} from './interfaces/definition';
|
import {ComponentDef, ComponentType} from './interfaces/definition';
|
||||||
import {LElementNode} from './interfaces/node';
|
import {LElementNode} from './interfaces/node';
|
||||||
import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
|
import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './interfaces/renderer';
|
||||||
|
@ -173,7 +172,7 @@ export function renderComponent<T>(
|
||||||
const hostNode = locateHostElement(rendererFactory, opts.host || componentDef.tag);
|
const hostNode = locateHostElement(rendererFactory, opts.host || componentDef.tag);
|
||||||
const oldView = enterView(
|
const oldView = enterView(
|
||||||
createLView(
|
createLView(
|
||||||
-1, rendererFactory.createRenderer(hostNode, componentDef.rendererType), {data: []}),
|
-1, rendererFactory.createRenderer(hostNode, componentDef.rendererType), createTView()),
|
||||||
null !);
|
null !);
|
||||||
try {
|
try {
|
||||||
// Create element node at index 0 in data array
|
// Create element node at index 0 in data array
|
||||||
|
|
|
@ -0,0 +1,153 @@
|
||||||
|
|
||||||
|
import {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
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Constants used by lifecycle hooks to determine when and how a hook should be called. */
|
||||||
|
export const enum LifecycleHookUtils {
|
||||||
|
/* Mask used to get the type of the lifecycle hook from flags in hook queue */
|
||||||
|
TYPE_MASK = 0b00000000000000000000000000000001,
|
||||||
|
|
||||||
|
/* Shift needed to get directive index from flags in hook queue */
|
||||||
|
INDX_SHIFT = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loops through the directives on a node and queues their afterContentInit,
|
||||||
|
* afterContentChecked, and onDestroy hooks, if they exist.
|
||||||
|
*/
|
||||||
|
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 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this is the first template pass, any ngOnInit or ngDoCheck hooks on the current directive
|
||||||
|
* will be queued on TView.initHooks.
|
||||||
|
*
|
||||||
|
* The directive index and hook type are encoded into one number (1st bit: type, remaining bits:
|
||||||
|
* directive index), then saved in the even indices of the initHooks array. The odd indices
|
||||||
|
* hold the hook functions themselves.
|
||||||
|
*
|
||||||
|
* @param index The index of the directive in LView.data
|
||||||
|
* @param hooks The static hooks map on the directive def
|
||||||
|
* @param tView The current TView
|
||||||
|
*/
|
||||||
|
export function queueInitHooks(index: number, hooks: LifecycleHooksMap, tView: TView): void {
|
||||||
|
if (tView.firstTemplatePass && hooks.onInit != null) {
|
||||||
|
const hookFlags = index << LifecycleHookUtils.INDX_SHIFT;
|
||||||
|
(tView.initHooks || (tView.initHooks = [])).push(hookFlags, hooks.onInit);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tView.firstTemplatePass && hooks.doCheck != null) {
|
||||||
|
const hookFlags = (index << LifecycleHookUtils.INDX_SHIFT) | LifecycleHook.ON_CHECK;
|
||||||
|
(tView.initHooks || (tView.initHooks = [])).push(hookFlags, hooks.doCheck);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls onInit and doCheck calls if they haven't already been called.
|
||||||
|
*
|
||||||
|
* @param currentView The current view
|
||||||
|
* @param initHooks The init hooks for this view
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls all afterContentInit and afterContentChecked hooks for the view, then splices
|
||||||
|
* 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);
|
||||||
|
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.
|
||||||
|
*
|
||||||
|
* @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]);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
checkIndex += 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Truncate once at the writeIndex
|
||||||
|
arr.length = writeIndex;
|
||||||
|
}
|
|
@ -24,8 +24,6 @@ export {InjectFlags, QUERY_READ_CONTAINER_REF, QUERY_READ_ELEMENT_REF, QUERY_REA
|
||||||
// clang-format off
|
// clang-format off
|
||||||
export {
|
export {
|
||||||
|
|
||||||
LifecycleHook,
|
|
||||||
|
|
||||||
NO_CHANGE as NC,
|
NO_CHANGE as NC,
|
||||||
|
|
||||||
bind as b,
|
bind as b,
|
||||||
|
@ -72,6 +70,9 @@ export {
|
||||||
query as Q,
|
query as Q,
|
||||||
queryRefresh as qR,
|
queryRefresh as qR,
|
||||||
} from './query';
|
} from './query';
|
||||||
|
|
||||||
|
export {LifecycleHook} from './hooks';
|
||||||
|
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|
|
@ -27,20 +27,9 @@ import {isNodeMatchingSelector} from './node_selector_matcher';
|
||||||
import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveType} from './interfaces/definition';
|
import {ComponentDef, ComponentTemplate, ComponentType, DirectiveDef, DirectiveType} from './interfaces/definition';
|
||||||
import {RComment, RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, ObjectOrientedRenderer3, RendererStyleFlags3} from './interfaces/renderer';
|
import {RComment, RElement, RText, Renderer3, RendererFactory3, ProceduralRenderer3, ObjectOrientedRenderer3, RendererStyleFlags3} from './interfaces/renderer';
|
||||||
import {isDifferent, stringify} from './util';
|
import {isDifferent, stringify} from './util';
|
||||||
|
import {LifecycleHook, executeViewHooks, executeContentHooks, queueLifecycleHooks, queueInitHooks, executeInitHooks} from './hooks';
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enum used by the lifecycle (l) instruction to determine which lifecycle hook is requesting
|
|
||||||
* processing.
|
|
||||||
*/
|
|
||||||
export const enum LifecycleHook {
|
|
||||||
ON_INIT = 1,
|
|
||||||
ON_DESTROY = 2,
|
|
||||||
ON_CHANGES = 4,
|
|
||||||
AFTER_INIT = 8,
|
|
||||||
AFTER_CHECKED = 16
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Directive (D) sets a property on all component instances using this constant as a key and the
|
* Directive (D) sets a property on all component instances using this constant as a key and the
|
||||||
* component's host node (LElement) as the value. This is used in methods like detectChanges to
|
* component's host node (LElement) as the value. This is used in methods like detectChanges to
|
||||||
|
@ -91,7 +80,7 @@ let tData: TData;
|
||||||
/** State of the current view being processed. */
|
/** State of the current view being processed. */
|
||||||
let currentView: LView;
|
let currentView: LView;
|
||||||
// The initialization has to be after the `let`, otherwise `createLView` can't see `let`.
|
// The initialization has to be after the `let`, otherwise `createLView` can't see `let`.
|
||||||
currentView = createLView(null !, null !, {data: []});
|
currentView = createLView(null !, null !, createTView());
|
||||||
|
|
||||||
let currentQuery: LQuery|null;
|
let currentQuery: LQuery|null;
|
||||||
|
|
||||||
|
@ -129,19 +118,6 @@ let bindingIndex: number;
|
||||||
*/
|
*/
|
||||||
let cleanup: any[]|null;
|
let 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
|
|
||||||
*/
|
|
||||||
let contentHooks: any[]|null;
|
|
||||||
|
|
||||||
/** Index in the data array at which view hooks begin to be stored. */
|
/** Index in the data array at which view hooks begin to be stored. */
|
||||||
let viewHookStartIndex: number|null;
|
let viewHookStartIndex: number|null;
|
||||||
|
|
||||||
|
@ -166,7 +142,6 @@ export function enterView(newView: LView, host: LElementNode | LViewNode | null)
|
||||||
|
|
||||||
viewHookStartIndex = newView.viewHookStartIndex;
|
viewHookStartIndex = newView.viewHookStartIndex;
|
||||||
cleanup = newView.cleanup;
|
cleanup = newView.cleanup;
|
||||||
contentHooks = newView.contentHooks;
|
|
||||||
renderer = newView.renderer;
|
renderer = newView.renderer;
|
||||||
|
|
||||||
if (host != null) {
|
if (host != null) {
|
||||||
|
@ -183,8 +158,10 @@ export function enterView(newView: LView, host: LElementNode | LViewNode | null)
|
||||||
* the direction of traversal (up or down the view tree) a bit clearer.
|
* the direction of traversal (up or down the view tree) a bit clearer.
|
||||||
*/
|
*/
|
||||||
export function leaveView(newView: LView): void {
|
export function leaveView(newView: LView): void {
|
||||||
executeViewHooks();
|
executeViewHooks(data, viewHookStartIndex);
|
||||||
|
currentView.initHooksCalled = false;
|
||||||
currentView.contentHooksCalled = false;
|
currentView.contentHooksCalled = false;
|
||||||
|
if (currentView.tView.firstTemplatePass) currentView.tView.firstTemplatePass = false;
|
||||||
enterView(newView, null);
|
enterView(newView, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,7 +186,8 @@ export function createLView(
|
||||||
template: template,
|
template: template,
|
||||||
context: context,
|
context: context,
|
||||||
dynamicViewCount: 0,
|
dynamicViewCount: 0,
|
||||||
contentHooksCalled: false
|
contentHooksCalled: false,
|
||||||
|
initHooksCalled: false
|
||||||
};
|
};
|
||||||
|
|
||||||
return newView;
|
return newView;
|
||||||
|
@ -508,7 +486,11 @@ function hack_findQueryName(
|
||||||
* @returns TView
|
* @returns TView
|
||||||
*/
|
*/
|
||||||
function getOrCreateTView(template: ComponentTemplate<any>): TView {
|
function getOrCreateTView(template: ComponentTemplate<any>): TView {
|
||||||
return template.ngPrivateData || (template.ngPrivateData = { data: [] } as never);
|
return template.ngPrivateData || (template.ngPrivateData = createTView() as never);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createTView(): TView {
|
||||||
|
return {data: [], firstTemplatePass: true, initHooks: null};
|
||||||
}
|
}
|
||||||
|
|
||||||
function setUpAttributes(native: RElement, attrs: string[]): void {
|
function setUpAttributes(native: RElement, attrs: string[]): void {
|
||||||
|
@ -627,35 +609,7 @@ export function elementEnd() {
|
||||||
ngDevMode && assertNodeType(previousOrParentNode, LNodeFlags.Element);
|
ngDevMode && assertNodeType(previousOrParentNode, LNodeFlags.Element);
|
||||||
const query = previousOrParentNode.query;
|
const query = previousOrParentNode.query;
|
||||||
query && query.addNode(previousOrParentNode);
|
query && query.addNode(previousOrParentNode);
|
||||||
queueLifecycleHooks();
|
queueLifecycleHooks(previousOrParentNode.flags, currentView);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loops through the directives on a node and queues their afterContentInit,
|
|
||||||
* afterContentChecked, and onDestroy hooks, if they exist.
|
|
||||||
*/
|
|
||||||
function queueLifecycleHooks(): 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 flags = previousOrParentNode.flags;
|
|
||||||
const size = (flags & LNodeFlags.SIZE_MASK) >> LNodeFlags.SIZE_SHIFT;
|
|
||||||
const start = flags >> LNodeFlags.INDX_SHIFT;
|
|
||||||
|
|
||||||
for (let i = start, end = start + size; i < end; i++) {
|
|
||||||
const instance = data[i];
|
|
||||||
if (instance.ngAfterContentInit != null) {
|
|
||||||
(contentHooks || (currentView.contentHooks = contentHooks = [
|
|
||||||
])).push(LifecycleHook.AFTER_INIT, instance.ngAfterContentInit, instance);
|
|
||||||
}
|
|
||||||
if (instance.ngAfterContentChecked != null) {
|
|
||||||
(contentHooks || (currentView.contentHooks = contentHooks = [
|
|
||||||
])).push(LifecycleHook.AFTER_CHECKED, instance.ngAfterContentChecked, instance);
|
|
||||||
}
|
|
||||||
if (instance.ngOnDestroy != null) {
|
|
||||||
(cleanup || (currentView.cleanup = cleanup = [])).push(instance.ngOnDestroy, instance);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -949,6 +903,11 @@ export function directiveCreate<T>(
|
||||||
if (tNode && tNode.attrs) {
|
if (tNode && tNode.attrs) {
|
||||||
setInputsFromAttrs<T>(instance, directiveDef !.inputs, tNode);
|
setInputsFromAttrs<T>(instance, directiveDef !.inputs, tNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Init hooks are queued now so ngOnInit is called in host components before
|
||||||
|
// any projected components.
|
||||||
|
queueInitHooks(index, directiveDef.lifecycleHooks, currentView.tView);
|
||||||
|
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1013,31 +972,13 @@ function generateInitialInputs(
|
||||||
* Accepts a lifecycle hook type and determines when and how the related lifecycle hook
|
* Accepts a lifecycle hook type and determines when and how the related lifecycle hook
|
||||||
* callback should run.
|
* callback should run.
|
||||||
*
|
*
|
||||||
* For the onInit lifecycle hook, it will return whether or not the ngOnInit() function
|
|
||||||
* should run. If so, ngOnInit() will be called outside of this function.
|
|
||||||
*
|
|
||||||
* e.g. l(LifecycleHook.ON_INIT) && ctx.ngOnInit();
|
|
||||||
*
|
|
||||||
* For the onDestroy lifecycle hook, this instruction also accepts an onDestroy
|
|
||||||
* method that should be stored and called internally when the parent view is being
|
|
||||||
* cleaned up.
|
|
||||||
*
|
|
||||||
* e.g. l(LifecycleHook.ON_DESTROY, ctx, ctx.onDestroy);
|
|
||||||
*
|
|
||||||
* @param lifecycle
|
* @param lifecycle
|
||||||
* @param self
|
* @param self
|
||||||
* @param method
|
* @param method
|
||||||
*/
|
*/
|
||||||
export function lifecycle(lifecycle: LifecycleHook.AFTER_INIT, self: any, method: Function): void;
|
export function lifecycle(lifecycle: LifecycleHook, self: any, method: Function): boolean {
|
||||||
export function lifecycle(
|
if (creationMode &&
|
||||||
lifecycle: LifecycleHook.AFTER_CHECKED, self: any, method: Function): void;
|
(lifecycle === LifecycleHook.ON_INIT || lifecycle === LifecycleHook.ON_CHECK)) {
|
||||||
export function lifecycle(lifecycle: LifecycleHook): boolean;
|
|
||||||
export function lifecycle(lifecycle: LifecycleHook, self?: any, method?: Function): boolean {
|
|
||||||
if (lifecycle === LifecycleHook.ON_INIT) {
|
|
||||||
return creationMode;
|
|
||||||
} else if (
|
|
||||||
creationMode &&
|
|
||||||
(lifecycle === LifecycleHook.AFTER_INIT || lifecycle === LifecycleHook.AFTER_CHECKED)) {
|
|
||||||
if (viewHookStartIndex == null) {
|
if (viewHookStartIndex == null) {
|
||||||
currentView.viewHookStartIndex = viewHookStartIndex = data.length;
|
currentView.viewHookStartIndex = viewHookStartIndex = data.length;
|
||||||
}
|
}
|
||||||
|
@ -1046,12 +987,6 @@ export function lifecycle(lifecycle: LifecycleHook, self?: any, method?: Functio
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Iterates over view hook functions and calls them. */
|
|
||||||
export function executeViewHooks(): void {
|
|
||||||
if (viewHookStartIndex == null) return;
|
|
||||||
executeHooksAndRemoveInits(data, viewHookStartIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
//////////////////////////
|
//////////////////////////
|
||||||
//// ViewContainer & View
|
//// ViewContainer & View
|
||||||
|
@ -1124,6 +1059,10 @@ export function containerRefreshStart(index: number): void {
|
||||||
ngDevMode && assertNodeType(previousOrParentNode, LNodeFlags.Container);
|
ngDevMode && assertNodeType(previousOrParentNode, LNodeFlags.Container);
|
||||||
isParent = true;
|
isParent = true;
|
||||||
(previousOrParentNode as LContainerNode).data.nextIndex = 0;
|
(previousOrParentNode as LContainerNode).data.nextIndex = 0;
|
||||||
|
|
||||||
|
// 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(currentView);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1210,7 +1149,7 @@ function getOrCreateEmbeddedTView(viewIndex: number, parent: LContainerNode): TV
|
||||||
ngDevMode && assertNodeType(parent, LNodeFlags.Container);
|
ngDevMode && assertNodeType(parent, LNodeFlags.Container);
|
||||||
const tContainer = (parent !.tNode as TContainerNode).data;
|
const tContainer = (parent !.tNode as TContainerNode).data;
|
||||||
if (viewIndex >= tContainer.length || tContainer[viewIndex] == null) {
|
if (viewIndex >= tContainer.length || tContainer[viewIndex] == null) {
|
||||||
tContainer[viewIndex] = { data: [] } as TView;
|
tContainer[viewIndex] = createTView();
|
||||||
}
|
}
|
||||||
return tContainer[viewIndex];
|
return tContainer[viewIndex];
|
||||||
}
|
}
|
||||||
|
@ -1261,7 +1200,8 @@ export const componentRefresh:
|
||||||
ngDevMode && assertDataInRange(directiveIndex);
|
ngDevMode && assertDataInRange(directiveIndex);
|
||||||
const hostView = element.data !;
|
const hostView = element.data !;
|
||||||
ngDevMode && assertNotEqual(hostView, null, 'hostView');
|
ngDevMode && assertNotEqual(hostView, null, 'hostView');
|
||||||
executeContentHooks();
|
executeInitHooks(currentView);
|
||||||
|
executeContentHooks(currentView);
|
||||||
const directive = data[directiveIndex];
|
const directive = data[directiveIndex];
|
||||||
const oldView = enterView(hostView, element);
|
const oldView = enterView(hostView, element);
|
||||||
try {
|
try {
|
||||||
|
@ -1273,49 +1213,6 @@ export const componentRefresh:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Calls all afterContentInit and afterContentChecked hooks for the view, then splices
|
|
||||||
* out afterContentInit hooks to prep for the next run in update mode.
|
|
||||||
*/
|
|
||||||
function executeContentHooks(): void {
|
|
||||||
if (contentHooks == null || currentView.contentHooksCalled) return;
|
|
||||||
executeHooksAndRemoveInits(contentHooks, 0);
|
|
||||||
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.
|
|
||||||
*
|
|
||||||
* @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]);
|
|
||||||
|
|
||||||
if (arr[checkIndex] === LifecycleHook.AFTER_CHECKED) {
|
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
checkIndex += 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Truncate once at the writeIndex
|
|
||||||
arr.length = writeIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instruction to distribute projectable nodes among <ng-content> occurrences in a given template.
|
* Instruction to distribute projectable nodes among <ng-content> occurrences in a given template.
|
||||||
* It takes all the selectors from the entire component's template and decides where
|
* It takes all the selectors from the entire component's template and decides where
|
||||||
|
|
|
@ -103,6 +103,16 @@ export interface LView {
|
||||||
*/
|
*/
|
||||||
contentHooks: any[]|null;
|
contentHooks: any[]|null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the ngOnInit and ngDoCheck hooks have been called in this change
|
||||||
|
* detection run.
|
||||||
|
*
|
||||||
|
* These two hooks are executed by the first Comp.r() instruction that runs OR the
|
||||||
|
* first cR instruction that runs (so inits are run for the top level view before
|
||||||
|
* any embedded views). For this reason, the call must be tracked.
|
||||||
|
*/
|
||||||
|
initHooksCalled: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not the content hooks have been called in this change detection run.
|
* Whether or not the content hooks have been called in this change detection run.
|
||||||
*
|
*
|
||||||
|
@ -194,7 +204,29 @@ export interface LViewOrLContainer {
|
||||||
*
|
*
|
||||||
* Stored on the template function as ngPrivateData.
|
* Stored on the template function as ngPrivateData.
|
||||||
*/
|
*/
|
||||||
export interface TView { data: TData; }
|
export interface TView {
|
||||||
|
/** Static data equivalent of LView.data[]. Contains TNodes and directive defs. */
|
||||||
|
data: TData;
|
||||||
|
|
||||||
|
/** Whether or not this template has been processed. */
|
||||||
|
firstTemplatePass: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Array of init 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 init hooks that should be executed for a view.
|
||||||
|
*
|
||||||
|
* Even indices: Flags (1st bit: hook type, remaining: directive index)
|
||||||
|
* Odd indices: Hook function
|
||||||
|
*/
|
||||||
|
export type HookData = (number | (() => void))[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Static data that corresponds to the instance-specific data array on an LView.
|
* Static data that corresponds to the instance-specific data array on an LView.
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {ElementRef, TemplateRef, ViewContainerRef} from '@angular/core';
|
||||||
import {defineComponent} from '../../src/render3/definition';
|
import {defineComponent} from '../../src/render3/definition';
|
||||||
import {InjectFlags, bloomAdd, bloomFindPossibleInjector, getOrCreateNodeInjector} from '../../src/render3/di';
|
import {InjectFlags, bloomAdd, bloomFindPossibleInjector, getOrCreateNodeInjector} from '../../src/render3/di';
|
||||||
import {C, E, PublicFeature, T, V, b, b2, cR, cr, defineDirective, e, inject, injectElementRef, injectTemplateRef, injectViewContainerRef, m, t, v} from '../../src/render3/index';
|
import {C, E, PublicFeature, T, V, b, b2, cR, cr, defineDirective, e, inject, injectElementRef, injectTemplateRef, injectViewContainerRef, m, t, v} from '../../src/render3/index';
|
||||||
import {createLNode, createLView, enterView, leaveView} from '../../src/render3/instructions';
|
import {createLNode, createLView, createTView, enterView, leaveView} from '../../src/render3/instructions';
|
||||||
import {LInjector} from '../../src/render3/interfaces/injector';
|
import {LInjector} from '../../src/render3/interfaces/injector';
|
||||||
import {LNodeFlags} from '../../src/render3/interfaces/node';
|
import {LNodeFlags} from '../../src/render3/interfaces/node';
|
||||||
|
|
||||||
|
@ -312,7 +312,7 @@ describe('di', () => {
|
||||||
|
|
||||||
describe('getOrCreateNodeInjector', () => {
|
describe('getOrCreateNodeInjector', () => {
|
||||||
it('should handle initial undefined state', () => {
|
it('should handle initial undefined state', () => {
|
||||||
const contentView = createLView(-1, null !, {data: []});
|
const contentView = createLView(-1, null !, createTView());
|
||||||
const oldView = enterView(contentView, null !);
|
const oldView = enterView(contentView, null !);
|
||||||
try {
|
try {
|
||||||
const parent = createLNode(0, LNodeFlags.Element, null, null);
|
const parent = createLNode(0, LNodeFlags.Element, null, null);
|
||||||
|
|
|
@ -6,7 +6,8 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {C, ComponentDef, ComponentTemplate, E, L, LifecycleHook, T, V, b, cR, cr, defineComponent, e, l, m, p, r, v, pD, P} from '../../src/render3/index';
|
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 {containerEl, renderToHtml} from './render_util';
|
import {containerEl, renderToHtml} from './render_util';
|
||||||
|
|
||||||
describe('lifecycles', () => {
|
describe('lifecycles', () => {
|
||||||
|
@ -52,8 +53,6 @@ describe('lifecycles', () => {
|
||||||
type: Component,
|
type: Component,
|
||||||
tag: name,
|
tag: name,
|
||||||
factory: () => new Component(),
|
factory: () => new Component(),
|
||||||
hostBindings: function(directiveIndex: number, elementIndex: number):
|
|
||||||
void { l(LifecycleHook.ON_INIT) && m<Component>(directiveIndex).ngOnInit(); },
|
|
||||||
inputs: {val: 'val'}, template
|
inputs: {val: 'val'}, template
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -220,6 +219,30 @@ describe('lifecycles', () => {
|
||||||
expect(events).toEqual(['comp1', 'projected1', 'comp2', 'projected2']);
|
expect(events).toEqual(['comp1', 'projected1', 'comp2', 'projected2']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should be called on directives after component', () => {
|
||||||
|
class Directive {
|
||||||
|
ngOnInit() { events.push('dir'); }
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({type: Directive, factory: () => new Directive()});
|
||||||
|
}
|
||||||
|
|
||||||
|
function Template(ctx: any, cm: boolean) {
|
||||||
|
if (cm) {
|
||||||
|
E(0, Comp, null, [Directive]);
|
||||||
|
e();
|
||||||
|
}
|
||||||
|
Comp.ngComponentDef.h(1, 0);
|
||||||
|
Comp.ngComponentDef.r(1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderToHtml(Template, {});
|
||||||
|
expect(events).toEqual(['comp', 'dir']);
|
||||||
|
|
||||||
|
renderToHtml(Template, {});
|
||||||
|
expect(events).toEqual(['comp', 'dir']);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
it('should call onInit properly in for loop', () => {
|
it('should call onInit properly in for loop', () => {
|
||||||
/**
|
/**
|
||||||
* <comp [val]="1"></comp>
|
* <comp [val]="1"></comp>
|
||||||
|
@ -334,22 +357,13 @@ describe('lifecycles', () => {
|
||||||
return class Component {
|
return class Component {
|
||||||
ngDoCheck() {
|
ngDoCheck() {
|
||||||
events.push(name);
|
events.push(name);
|
||||||
allEvents.push('ngDoCheck ' + name);
|
allEvents.push('check ' + name);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit() { allEvents.push('ngOnInit ' + name); }
|
ngOnInit() { allEvents.push('init ' + name); }
|
||||||
|
|
||||||
static ngComponentDef = defineComponent({
|
static ngComponentDef =
|
||||||
type: Component,
|
defineComponent({type: Component, tag: name, factory: () => new Component(), template});
|
||||||
tag: name,
|
|
||||||
factory: () => new Component(),
|
|
||||||
hostBindings: function(
|
|
||||||
this: ComponentDef<Component>, directiveIndex: number, elementIndex: number): void {
|
|
||||||
l(LifecycleHook.ON_INIT) && m<Component>(directiveIndex).ngOnInit();
|
|
||||||
m<Component>(directiveIndex).ngDoCheck();
|
|
||||||
},
|
|
||||||
template
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -402,10 +416,10 @@ describe('lifecycles', () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderToHtml(Template, {});
|
renderToHtml(Template, {});
|
||||||
expect(allEvents).toEqual(['ngOnInit comp', 'ngDoCheck comp']);
|
expect(allEvents).toEqual(['init comp', 'check comp']);
|
||||||
|
|
||||||
renderToHtml(Template, {});
|
renderToHtml(Template, {});
|
||||||
expect(allEvents).toEqual(['ngOnInit comp', 'ngDoCheck comp', 'ngDoCheck comp']);
|
expect(allEvents).toEqual(['init comp', 'check comp', 'check comp']);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -829,8 +843,8 @@ describe('lifecycles', () => {
|
||||||
refresh: (directiveIndex: number, elementIndex: number) => {
|
refresh: (directiveIndex: number, elementIndex: number) => {
|
||||||
r(directiveIndex, elementIndex, template);
|
r(directiveIndex, elementIndex, template);
|
||||||
const comp = m(directiveIndex) as Component;
|
const comp = m(directiveIndex) as Component;
|
||||||
l(LifecycleHook.AFTER_INIT, comp, comp.ngAfterViewInit);
|
l(LifecycleHook.ON_INIT, comp, comp.ngAfterViewInit);
|
||||||
l(LifecycleHook.AFTER_CHECKED, comp, comp.ngAfterViewChecked);
|
l(LifecycleHook.ON_CHECK, comp, comp.ngAfterViewChecked);
|
||||||
},
|
},
|
||||||
inputs: {val: 'val'},
|
inputs: {val: 'val'},
|
||||||
template: template
|
template: template
|
||||||
|
@ -1693,16 +1707,11 @@ describe('lifecycles', () => {
|
||||||
type: Component,
|
type: Component,
|
||||||
tag: name,
|
tag: name,
|
||||||
factory: () => new Component(),
|
factory: () => new Component(),
|
||||||
hostBindings: function(directiveIndex: number, elementIndex: number): void {
|
|
||||||
const comp = D(directiveIndex) as Component;
|
|
||||||
l(LifecycleHook.ON_INIT) && comp.ngOnInit();
|
|
||||||
comp.ngDoCheck();
|
|
||||||
},
|
|
||||||
refresh: function(directiveIndex: number, elementIndex: number): void {
|
refresh: function(directiveIndex: number, elementIndex: number): void {
|
||||||
r(directiveIndex, elementIndex, template);
|
r(directiveIndex, elementIndex, template);
|
||||||
const comp = D(directiveIndex) as Component;
|
const comp = m(directiveIndex) as Component;
|
||||||
l(LifecycleHook.AFTER_INIT, comp, comp.ngAfterViewInit);
|
l(LifecycleHook.ON_INIT, comp, comp.ngAfterViewInit);
|
||||||
l(LifecycleHook.AFTER_CHECKED, comp, comp.ngAfterViewChecked);
|
l(LifecycleHook.ON_CHECK, comp, comp.ngAfterViewChecked);
|
||||||
},
|
},
|
||||||
inputs: {val: 'val'}, template
|
inputs: {val: 'val'}, template
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue