Throwing an exception in a lifecycle event will delay but not prevent an Init method, such as `ngOnInit`, `ngAfterContentInit`, or `ngAfterViewInit`, from being called. Also, calling `detectChanges()` in a way that causes duplicate change detection (such as a child component causing a parent to call `detectChanges()` on its own `ChangeDetectorRef`, will no longer prevent change `ngOnInit`, `ngAfterContentInit` and `ngAfterViewInit` from being called. With this change lifecycle methods are still not guarenteed to be called but the Init methods will be called if at least one change detection pass on its view is completed. Fixes: #17035 PR Close #20258
565 lines
21 KiB
TypeScript
565 lines
21 KiB
TypeScript
/**
|
|
* @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
|
|
*/
|
|
|
|
import {ChangeDetectorRef, SimpleChange, SimpleChanges, WrappedValue} from '../change_detection/change_detection';
|
|
import {Injector, resolveForwardRef} from '../di';
|
|
import {ElementRef} from '../linker/element_ref';
|
|
import {TemplateRef} from '../linker/template_ref';
|
|
import {ViewContainerRef} from '../linker/view_container_ref';
|
|
import {Renderer as RendererV1, Renderer2} from '../render/api';
|
|
|
|
import {createChangeDetectorRef, createInjector, createRendererV1} from './refs';
|
|
import {BindingDef, BindingFlags, DepDef, DepFlags, NodeDef, NodeFlags, OutputDef, OutputType, ProviderData, QueryValueType, Services, ViewData, ViewFlags, ViewState, asElementData, asProviderData, shouldCallLifecycleInitHook} from './types';
|
|
import {calcBindingFlags, checkBinding, dispatchEvent, isComponentView, splitDepsDsl, splitMatchedQueriesDsl, tokenKey, viewParentEl} from './util';
|
|
|
|
const RendererV1TokenKey = tokenKey(RendererV1);
|
|
const Renderer2TokenKey = tokenKey(Renderer2);
|
|
const ElementRefTokenKey = tokenKey(ElementRef);
|
|
const ViewContainerRefTokenKey = tokenKey(ViewContainerRef);
|
|
const TemplateRefTokenKey = tokenKey(TemplateRef);
|
|
const ChangeDetectorRefTokenKey = tokenKey(ChangeDetectorRef);
|
|
const InjectorRefTokenKey = tokenKey(Injector);
|
|
|
|
export function directiveDef(
|
|
checkIndex: number, flags: NodeFlags,
|
|
matchedQueries: null | [string | number, QueryValueType][], childCount: number, ctor: any,
|
|
deps: ([DepFlags, any] | any)[], props?: null | {[name: string]: [number, string]},
|
|
outputs?: null | {[name: string]: string}): NodeDef {
|
|
const bindings: BindingDef[] = [];
|
|
if (props) {
|
|
for (let prop in props) {
|
|
const [bindingIndex, nonMinifiedName] = props[prop];
|
|
bindings[bindingIndex] = {
|
|
flags: BindingFlags.TypeProperty,
|
|
name: prop, nonMinifiedName,
|
|
ns: null,
|
|
securityContext: null,
|
|
suffix: null
|
|
};
|
|
}
|
|
}
|
|
const outputDefs: OutputDef[] = [];
|
|
if (outputs) {
|
|
for (let propName in outputs) {
|
|
outputDefs.push(
|
|
{type: OutputType.DirectiveOutput, propName, target: null, eventName: outputs[propName]});
|
|
}
|
|
}
|
|
flags |= NodeFlags.TypeDirective;
|
|
return _def(
|
|
checkIndex, flags, matchedQueries, childCount, ctor, ctor, deps, bindings, outputDefs);
|
|
}
|
|
|
|
export function pipeDef(flags: NodeFlags, ctor: any, deps: ([DepFlags, any] | any)[]): NodeDef {
|
|
flags |= NodeFlags.TypePipe;
|
|
return _def(-1, flags, null, 0, ctor, ctor, deps);
|
|
}
|
|
|
|
export function providerDef(
|
|
flags: NodeFlags, matchedQueries: null | [string | number, QueryValueType][], token: any,
|
|
value: any, deps: ([DepFlags, any] | any)[]): NodeDef {
|
|
return _def(-1, flags, matchedQueries, 0, token, value, deps);
|
|
}
|
|
|
|
export function _def(
|
|
checkIndex: number, flags: NodeFlags,
|
|
matchedQueriesDsl: [string | number, QueryValueType][] | null, childCount: number, token: any,
|
|
value: any, deps: ([DepFlags, any] | any)[], bindings?: BindingDef[],
|
|
outputs?: OutputDef[]): NodeDef {
|
|
const {matchedQueries, references, matchedQueryIds} = splitMatchedQueriesDsl(matchedQueriesDsl);
|
|
if (!outputs) {
|
|
outputs = [];
|
|
}
|
|
if (!bindings) {
|
|
bindings = [];
|
|
}
|
|
// Need to resolve forwardRefs as e.g. for `useValue` we
|
|
// lowered the expression and then stopped evaluating it,
|
|
// i.e. also didn't unwrap it.
|
|
value = resolveForwardRef(value);
|
|
|
|
const depDefs = splitDepsDsl(deps);
|
|
|
|
return {
|
|
// will bet set by the view definition
|
|
nodeIndex: -1,
|
|
parent: null,
|
|
renderParent: null,
|
|
bindingIndex: -1,
|
|
outputIndex: -1,
|
|
// regular values
|
|
checkIndex,
|
|
flags,
|
|
childFlags: 0,
|
|
directChildFlags: 0,
|
|
childMatchedQueries: 0, matchedQueries, matchedQueryIds, references,
|
|
ngContentIndex: -1, childCount, bindings,
|
|
bindingFlags: calcBindingFlags(bindings), outputs,
|
|
element: null,
|
|
provider: {token, value, deps: depDefs},
|
|
text: null,
|
|
query: null,
|
|
ngContent: null
|
|
};
|
|
}
|
|
|
|
export function createProviderInstance(view: ViewData, def: NodeDef): any {
|
|
return _createProviderInstance(view, def);
|
|
}
|
|
|
|
export function createPipeInstance(view: ViewData, def: NodeDef): any {
|
|
// deps are looked up from component.
|
|
let compView = view;
|
|
while (compView.parent && !isComponentView(compView)) {
|
|
compView = compView.parent;
|
|
}
|
|
// pipes can see the private services of the component
|
|
const allowPrivateServices = true;
|
|
// pipes are always eager and classes!
|
|
return createClass(
|
|
compView.parent !, viewParentEl(compView) !, allowPrivateServices, def.provider !.value,
|
|
def.provider !.deps);
|
|
}
|
|
|
|
export function createDirectiveInstance(view: ViewData, def: NodeDef): any {
|
|
// components can see other private services, other directives can't.
|
|
const allowPrivateServices = (def.flags & NodeFlags.Component) > 0;
|
|
// directives are always eager and classes!
|
|
const instance = createClass(
|
|
view, def.parent !, allowPrivateServices, def.provider !.value, def.provider !.deps);
|
|
if (def.outputs.length) {
|
|
for (let i = 0; i < def.outputs.length; i++) {
|
|
const output = def.outputs[i];
|
|
const subscription = instance[output.propName !].subscribe(
|
|
eventHandlerClosure(view, def.parent !.nodeIndex, output.eventName));
|
|
view.disposables ![def.outputIndex + i] = subscription.unsubscribe.bind(subscription);
|
|
}
|
|
}
|
|
return instance;
|
|
}
|
|
|
|
function eventHandlerClosure(view: ViewData, index: number, eventName: string) {
|
|
return (event: any) => dispatchEvent(view, index, eventName, event);
|
|
}
|
|
|
|
export function checkAndUpdateDirectiveInline(
|
|
view: ViewData, def: NodeDef, v0: any, v1: any, v2: any, v3: any, v4: any, v5: any, v6: any,
|
|
v7: any, v8: any, v9: any): boolean {
|
|
const providerData = asProviderData(view, def.nodeIndex);
|
|
const directive = providerData.instance;
|
|
let changed = false;
|
|
let changes: SimpleChanges = undefined !;
|
|
const bindLen = def.bindings.length;
|
|
if (bindLen > 0 && checkBinding(view, def, 0, v0)) {
|
|
changed = true;
|
|
changes = updateProp(view, providerData, def, 0, v0, changes);
|
|
}
|
|
if (bindLen > 1 && checkBinding(view, def, 1, v1)) {
|
|
changed = true;
|
|
changes = updateProp(view, providerData, def, 1, v1, changes);
|
|
}
|
|
if (bindLen > 2 && checkBinding(view, def, 2, v2)) {
|
|
changed = true;
|
|
changes = updateProp(view, providerData, def, 2, v2, changes);
|
|
}
|
|
if (bindLen > 3 && checkBinding(view, def, 3, v3)) {
|
|
changed = true;
|
|
changes = updateProp(view, providerData, def, 3, v3, changes);
|
|
}
|
|
if (bindLen > 4 && checkBinding(view, def, 4, v4)) {
|
|
changed = true;
|
|
changes = updateProp(view, providerData, def, 4, v4, changes);
|
|
}
|
|
if (bindLen > 5 && checkBinding(view, def, 5, v5)) {
|
|
changed = true;
|
|
changes = updateProp(view, providerData, def, 5, v5, changes);
|
|
}
|
|
if (bindLen > 6 && checkBinding(view, def, 6, v6)) {
|
|
changed = true;
|
|
changes = updateProp(view, providerData, def, 6, v6, changes);
|
|
}
|
|
if (bindLen > 7 && checkBinding(view, def, 7, v7)) {
|
|
changed = true;
|
|
changes = updateProp(view, providerData, def, 7, v7, changes);
|
|
}
|
|
if (bindLen > 8 && checkBinding(view, def, 8, v8)) {
|
|
changed = true;
|
|
changes = updateProp(view, providerData, def, 8, v8, changes);
|
|
}
|
|
if (bindLen > 9 && checkBinding(view, def, 9, v9)) {
|
|
changed = true;
|
|
changes = updateProp(view, providerData, def, 9, v9, changes);
|
|
}
|
|
if (changes) {
|
|
directive.ngOnChanges(changes);
|
|
}
|
|
if ((def.flags & NodeFlags.OnInit) &&
|
|
shouldCallLifecycleInitHook(view, ViewState.InitState_CallingOnInit, def.nodeIndex)) {
|
|
directive.ngOnInit();
|
|
}
|
|
if (def.flags & NodeFlags.DoCheck) {
|
|
directive.ngDoCheck();
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
export function checkAndUpdateDirectiveDynamic(
|
|
view: ViewData, def: NodeDef, values: any[]): boolean {
|
|
const providerData = asProviderData(view, def.nodeIndex);
|
|
const directive = providerData.instance;
|
|
let changed = false;
|
|
let changes: SimpleChanges = undefined !;
|
|
for (let i = 0; i < values.length; i++) {
|
|
if (checkBinding(view, def, i, values[i])) {
|
|
changed = true;
|
|
changes = updateProp(view, providerData, def, i, values[i], changes);
|
|
}
|
|
}
|
|
if (changes) {
|
|
directive.ngOnChanges(changes);
|
|
}
|
|
if ((def.flags & NodeFlags.OnInit) &&
|
|
shouldCallLifecycleInitHook(view, ViewState.InitState_CallingOnInit, def.nodeIndex)) {
|
|
directive.ngOnInit();
|
|
}
|
|
if (def.flags & NodeFlags.DoCheck) {
|
|
directive.ngDoCheck();
|
|
}
|
|
return changed;
|
|
}
|
|
|
|
function _createProviderInstance(view: ViewData, def: NodeDef): any {
|
|
// private services can see other private services
|
|
const allowPrivateServices = (def.flags & NodeFlags.PrivateProvider) > 0;
|
|
const providerDef = def.provider;
|
|
switch (def.flags & NodeFlags.Types) {
|
|
case NodeFlags.TypeClassProvider:
|
|
return createClass(
|
|
view, def.parent !, allowPrivateServices, providerDef !.value, providerDef !.deps);
|
|
case NodeFlags.TypeFactoryProvider:
|
|
return callFactory(
|
|
view, def.parent !, allowPrivateServices, providerDef !.value, providerDef !.deps);
|
|
case NodeFlags.TypeUseExistingProvider:
|
|
return resolveDep(view, def.parent !, allowPrivateServices, providerDef !.deps[0]);
|
|
case NodeFlags.TypeValueProvider:
|
|
return providerDef !.value;
|
|
}
|
|
}
|
|
|
|
function createClass(
|
|
view: ViewData, elDef: NodeDef, allowPrivateServices: boolean, ctor: any, deps: DepDef[]): any {
|
|
const len = deps.length;
|
|
switch (len) {
|
|
case 0:
|
|
return new ctor();
|
|
case 1:
|
|
return new ctor(resolveDep(view, elDef, allowPrivateServices, deps[0]));
|
|
case 2:
|
|
return new ctor(
|
|
resolveDep(view, elDef, allowPrivateServices, deps[0]),
|
|
resolveDep(view, elDef, allowPrivateServices, deps[1]));
|
|
case 3:
|
|
return new ctor(
|
|
resolveDep(view, elDef, allowPrivateServices, deps[0]),
|
|
resolveDep(view, elDef, allowPrivateServices, deps[1]),
|
|
resolveDep(view, elDef, allowPrivateServices, deps[2]));
|
|
default:
|
|
const depValues = new Array(len);
|
|
for (let i = 0; i < len; i++) {
|
|
depValues[i] = resolveDep(view, elDef, allowPrivateServices, deps[i]);
|
|
}
|
|
return new ctor(...depValues);
|
|
}
|
|
}
|
|
|
|
function callFactory(
|
|
view: ViewData, elDef: NodeDef, allowPrivateServices: boolean, factory: any,
|
|
deps: DepDef[]): any {
|
|
const len = deps.length;
|
|
switch (len) {
|
|
case 0:
|
|
return factory();
|
|
case 1:
|
|
return factory(resolveDep(view, elDef, allowPrivateServices, deps[0]));
|
|
case 2:
|
|
return factory(
|
|
resolveDep(view, elDef, allowPrivateServices, deps[0]),
|
|
resolveDep(view, elDef, allowPrivateServices, deps[1]));
|
|
case 3:
|
|
return factory(
|
|
resolveDep(view, elDef, allowPrivateServices, deps[0]),
|
|
resolveDep(view, elDef, allowPrivateServices, deps[1]),
|
|
resolveDep(view, elDef, allowPrivateServices, deps[2]));
|
|
default:
|
|
const depValues = Array(len);
|
|
for (let i = 0; i < len; i++) {
|
|
depValues[i] = resolveDep(view, elDef, allowPrivateServices, deps[i]);
|
|
}
|
|
return factory(...depValues);
|
|
}
|
|
}
|
|
|
|
// This default value is when checking the hierarchy for a token.
|
|
//
|
|
// It means both:
|
|
// - the token is not provided by the current injector,
|
|
// - only the element injectors should be checked (ie do not check module injectors
|
|
//
|
|
// mod1
|
|
// /
|
|
// el1 mod2
|
|
// \ /
|
|
// el2
|
|
//
|
|
// When requesting el2.injector.get(token), we should check in the following order and return the
|
|
// first found value:
|
|
// - el2.injector.get(token, default)
|
|
// - el1.injector.get(token, NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR) -> do not check the module
|
|
// - mod2.injector.get(token, default)
|
|
export const NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR = {};
|
|
|
|
export function resolveDep(
|
|
view: ViewData, elDef: NodeDef, allowPrivateServices: boolean, depDef: DepDef,
|
|
notFoundValue: any = Injector.THROW_IF_NOT_FOUND): any {
|
|
if (depDef.flags & DepFlags.Value) {
|
|
return depDef.token;
|
|
}
|
|
const startView = view;
|
|
if (depDef.flags & DepFlags.Optional) {
|
|
notFoundValue = null;
|
|
}
|
|
const tokenKey = depDef.tokenKey;
|
|
|
|
if (tokenKey === ChangeDetectorRefTokenKey) {
|
|
// directives on the same element as a component should be able to control the change detector
|
|
// of that component as well.
|
|
allowPrivateServices = !!(elDef && elDef.element !.componentView);
|
|
}
|
|
|
|
if (elDef && (depDef.flags & DepFlags.SkipSelf)) {
|
|
allowPrivateServices = false;
|
|
elDef = elDef.parent !;
|
|
}
|
|
|
|
while (view) {
|
|
if (elDef) {
|
|
switch (tokenKey) {
|
|
case RendererV1TokenKey: {
|
|
const compView = findCompView(view, elDef, allowPrivateServices);
|
|
return createRendererV1(compView);
|
|
}
|
|
case Renderer2TokenKey: {
|
|
const compView = findCompView(view, elDef, allowPrivateServices);
|
|
return compView.renderer;
|
|
}
|
|
case ElementRefTokenKey:
|
|
return new ElementRef(asElementData(view, elDef.nodeIndex).renderElement);
|
|
case ViewContainerRefTokenKey:
|
|
return asElementData(view, elDef.nodeIndex).viewContainer;
|
|
case TemplateRefTokenKey: {
|
|
if (elDef.element !.template) {
|
|
return asElementData(view, elDef.nodeIndex).template;
|
|
}
|
|
break;
|
|
}
|
|
case ChangeDetectorRefTokenKey: {
|
|
let cdView = findCompView(view, elDef, allowPrivateServices);
|
|
return createChangeDetectorRef(cdView);
|
|
}
|
|
case InjectorRefTokenKey:
|
|
return createInjector(view, elDef);
|
|
default:
|
|
const providerDef =
|
|
(allowPrivateServices ? elDef.element !.allProviders :
|
|
elDef.element !.publicProviders) ![tokenKey];
|
|
if (providerDef) {
|
|
let providerData = asProviderData(view, providerDef.nodeIndex);
|
|
if (!providerData) {
|
|
providerData = {instance: _createProviderInstance(view, providerDef)};
|
|
view.nodes[providerDef.nodeIndex] = providerData as any;
|
|
}
|
|
return providerData.instance;
|
|
}
|
|
}
|
|
}
|
|
allowPrivateServices = isComponentView(view);
|
|
elDef = viewParentEl(view) !;
|
|
view = view.parent !;
|
|
}
|
|
|
|
const value = startView.root.injector.get(depDef.token, NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR);
|
|
|
|
if (value !== NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR ||
|
|
notFoundValue === NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR) {
|
|
// Return the value from the root element injector when
|
|
// - it provides it
|
|
// (value !== NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR)
|
|
// - the module injector should not be checked
|
|
// (notFoundValue === NOT_FOUND_CHECK_ONLY_ELEMENT_INJECTOR)
|
|
return value;
|
|
}
|
|
|
|
return startView.root.ngModule.injector.get(depDef.token, notFoundValue);
|
|
}
|
|
|
|
function findCompView(view: ViewData, elDef: NodeDef, allowPrivateServices: boolean) {
|
|
let compView: ViewData;
|
|
if (allowPrivateServices) {
|
|
compView = asElementData(view, elDef.nodeIndex).componentView;
|
|
} else {
|
|
compView = view;
|
|
while (compView.parent && !isComponentView(compView)) {
|
|
compView = compView.parent;
|
|
}
|
|
}
|
|
return compView;
|
|
}
|
|
|
|
function updateProp(
|
|
view: ViewData, providerData: ProviderData, def: NodeDef, bindingIdx: number, value: any,
|
|
changes: SimpleChanges): SimpleChanges {
|
|
if (def.flags & NodeFlags.Component) {
|
|
const compView = asElementData(view, def.parent !.nodeIndex).componentView;
|
|
if (compView.def.flags & ViewFlags.OnPush) {
|
|
compView.state |= ViewState.ChecksEnabled;
|
|
}
|
|
}
|
|
const binding = def.bindings[bindingIdx];
|
|
const propName = binding.name !;
|
|
// Note: This is still safe with Closure Compiler as
|
|
// the user passed in the property name as an object has to `providerDef`,
|
|
// so Closure Compiler will have renamed the property correctly already.
|
|
providerData.instance[propName] = value;
|
|
if (def.flags & NodeFlags.OnChanges) {
|
|
changes = changes || {};
|
|
let oldValue = view.oldValues[def.bindingIndex + bindingIdx];
|
|
if (oldValue instanceof WrappedValue) {
|
|
oldValue = oldValue.wrapped;
|
|
}
|
|
const binding = def.bindings[bindingIdx];
|
|
changes[binding.nonMinifiedName !] =
|
|
new SimpleChange(oldValue, value, (view.state & ViewState.FirstCheck) !== 0);
|
|
}
|
|
view.oldValues[def.bindingIndex + bindingIdx] = value;
|
|
return changes;
|
|
}
|
|
|
|
// This function calls the ngAfterContentCheck, ngAfterContentInit,
|
|
// ngAfterViewCheck, and ngAfterViewInit lifecycle hooks (depending on the node
|
|
// flags in lifecycle). Unlike ngDoCheck, ngOnChanges and ngOnInit, which are
|
|
// called during a pre-order traversal of the view tree (that is calling the
|
|
// parent hooks before the child hooks) these events are sent in using a
|
|
// post-order traversal of the tree (children before parents). This changes the
|
|
// meaning of initIndex in the view state. For ngOnInit, initIndex tracks the
|
|
// expected nodeIndex which a ngOnInit should be called. When sending
|
|
// ngAfterContentInit and ngAfterViewInit it is the expected count of
|
|
// ngAfterContentInit or ngAfterViewInit methods that have been called. This
|
|
// ensure that dispite being called recursively or after picking up after an
|
|
// exception, the ngAfterContentInit or ngAfterViewInit will be called on the
|
|
// correct nodes. Consider for example, the following (where E is an element
|
|
// and D is a directive)
|
|
// Tree: pre-order index post-order index
|
|
// E1 0 6
|
|
// E2 1 1
|
|
// D3 2 0
|
|
// E4 3 5
|
|
// E5 4 4
|
|
// E6 5 2
|
|
// E7 6 3
|
|
// As can be seen, the post-order index has an unclear relationship to the
|
|
// pre-order index (postOrderIndex === preOrderIndex - parentCount +
|
|
// childCount). Since number of calls to ngAfterContentInit and ngAfterViewInit
|
|
// are stable (will be the same for the same view regardless of exceptions or
|
|
// recursion) we just need to count them which will roughly correspond to the
|
|
// post-order index (it skips elements and directives that do not have
|
|
// lifecycle hooks).
|
|
//
|
|
// For example, if an exception is raised in the E6.onAfterViewInit() the
|
|
// initIndex is left at 3 (by shouldCallLifecycleInitHook() which set it to
|
|
// initIndex + 1). When checkAndUpdateView() is called again D3, E2 and E6 will
|
|
// not have their ngAfterViewInit() called but, starting with E7, the rest of
|
|
// the view will begin getting ngAfterViewInit() called until a check and
|
|
// pass is complete.
|
|
//
|
|
// This algorthim also handles recursion. Consider if E4's ngAfterViewInit()
|
|
// indirectly calls E1's ChangeDetectorRef.detectChanges(). The expected
|
|
// initIndex is set to 6, the recusive checkAndUpdateView() starts walk again.
|
|
// D3, E2, E6, E7, E5 and E4 are skipped, ngAfterViewInit() is called on E1.
|
|
// When the recursion returns the initIndex will be 7 so E1 is skipped as it
|
|
// has already been called in the recursively called checkAnUpdateView().
|
|
export function callLifecycleHooksChildrenFirst(view: ViewData, lifecycles: NodeFlags) {
|
|
if (!(view.def.nodeFlags & lifecycles)) {
|
|
return;
|
|
}
|
|
const nodes = view.def.nodes;
|
|
let initIndex = 0;
|
|
for (let i = 0; i < nodes.length; i++) {
|
|
const nodeDef = nodes[i];
|
|
let parent = nodeDef.parent;
|
|
if (!parent && nodeDef.flags & lifecycles) {
|
|
// matching root node (e.g. a pipe)
|
|
callProviderLifecycles(view, i, nodeDef.flags & lifecycles, initIndex++);
|
|
}
|
|
if ((nodeDef.childFlags & lifecycles) === 0) {
|
|
// no child matches one of the lifecycles
|
|
i += nodeDef.childCount;
|
|
}
|
|
while (parent && (parent.flags & NodeFlags.TypeElement) &&
|
|
i === parent.nodeIndex + parent.childCount) {
|
|
// last child of an element
|
|
if (parent.directChildFlags & lifecycles) {
|
|
initIndex = callElementProvidersLifecycles(view, parent, lifecycles, initIndex);
|
|
}
|
|
parent = parent.parent;
|
|
}
|
|
}
|
|
}
|
|
|
|
function callElementProvidersLifecycles(
|
|
view: ViewData, elDef: NodeDef, lifecycles: NodeFlags, initIndex: number): number {
|
|
for (let i = elDef.nodeIndex + 1; i <= elDef.nodeIndex + elDef.childCount; i++) {
|
|
const nodeDef = view.def.nodes[i];
|
|
if (nodeDef.flags & lifecycles) {
|
|
callProviderLifecycles(view, i, nodeDef.flags & lifecycles, initIndex++);
|
|
}
|
|
// only visit direct children
|
|
i += nodeDef.childCount;
|
|
}
|
|
return initIndex;
|
|
}
|
|
|
|
function callProviderLifecycles(
|
|
view: ViewData, index: number, lifecycles: NodeFlags, initIndex: number) {
|
|
const providerData = asProviderData(view, index);
|
|
if (!providerData) {
|
|
return;
|
|
}
|
|
const provider = providerData.instance;
|
|
if (!provider) {
|
|
return;
|
|
}
|
|
Services.setCurrentNode(view, index);
|
|
if (lifecycles & NodeFlags.AfterContentInit &&
|
|
shouldCallLifecycleInitHook(view, ViewState.InitState_CallingAfterContentInit, initIndex)) {
|
|
provider.ngAfterContentInit();
|
|
}
|
|
if (lifecycles & NodeFlags.AfterContentChecked) {
|
|
provider.ngAfterContentChecked();
|
|
}
|
|
if (lifecycles & NodeFlags.AfterViewInit &&
|
|
shouldCallLifecycleInitHook(view, ViewState.InitState_CallingAfterViewInit, initIndex)) {
|
|
provider.ngAfterViewInit();
|
|
}
|
|
if (lifecycles & NodeFlags.AfterViewChecked) {
|
|
provider.ngAfterViewChecked();
|
|
}
|
|
if (lifecycles & NodeFlags.OnDestroy) {
|
|
provider.ngOnDestroy();
|
|
}
|
|
}
|