Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(core): ensure init lifecycle events are called #20258

Closed
wants to merge 1 commit into from

Conversation

chuckjaz
Copy link
Contributor

@chuckjaz chuckjaz commented Nov 8, 2017

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 Type

What kind of change does this PR introduce?

[x] Bugfix

What is the current behavior?

Issue Number: #17035

Initialization lifecycle methods might not be called on an component even after a successful check of its view has been completed.

What is the new behavior?

The successful completion of a check of a view will ensure that all components and directives will have the initialization methods, such as ngOnInit, ngAfterContentInit and ngAfterViewInit, are called once an only once.

Does this PR introduce a breaking change?

[ ] Yes
[x] No

@mary-poppins
Copy link

You can preview 75fd092 at https://pr20258-75fd092.ngbuilds.io/.

// lifecycle methods.
export function checkCycle(view: ViewData, cycle: ViewState): boolean {
const state = view.state;
if ((state & ViewState.InitStateMask) === ((cycle - 1) & ViewState.InitStateMask)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either this code is way too smart for me to understand w/o thinking for 2h or it is wrong...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which in either case is probably a code smell

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Either way it should have a comment. I will add one.

@vicb vicb added action: review The PR is still awaiting reviews from at least one requested reviewer area: core Issues related to the framework runtime labels Nov 8, 2017
@chuckjaz
Copy link
Contributor Author

chuckjaz commented Nov 8, 2017

Changes need to land in G3 before this can land.

@mary-poppins
Copy link

You can preview ba73309 at https://pr20258-ba73309.ngbuilds.io/.

@mary-poppins
Copy link

You can preview 47f5027 at https://pr20258-47f5027.ngbuilds.io/.

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);
callProviderLifecycles(view, i, nodeDef.flags & lifecycles, initIndex++);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit(style): what about ++ing initIndex after the call ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a a performance and size critical part of the code. Moving the ++ after the call increases the size of this expression in a way that is impossible for closure or rollup to optimize so I would prefer to keep the current code.

Copy link
Contributor

@vicb vicb Nov 9, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this is critical there should be a comment IMO.

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);
callProviderLifecycles(view, i, nodeDef.flags & lifecycles, initIndex++);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment here

// thrown while checkAndUpdateView is running, checkAndUpdateView starts over
// from the beginning. This ensures the state is monotonically increasing,
// terminating in the AfterInit state, which ensures the Init methods are called
// atleast once and only once.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • minor typo at+ +least
  • use backquotes for code refs in the comments above ?

}

function forEachMethod(methods: LifetimeMethods, cb: (method: LifetimeMethods) => void) {
for (let method = LifetimeMethods.ngOnInit; method <= LifetimeMethods.All; method <<= 1) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

method <= LifetimeMethods.All is too smart for me.
Probably the semantic is not correct ? should it be method <= LifetimeMethods. ngDoCheck

I would go for simple and more maintainable version for(const method of [explicit list])

@mary-poppins
Copy link

You can preview 2e21554 at https://pr20258-2e21554.ngbuilds.io/.

@@ -198,7 +198,8 @@ export function checkAndUpdateDirectiveInline(
if (changes) {
directive.ngOnChanges(changes);
}
if ((view.state & ViewState.FirstCheck) && (def.flags & NodeFlags.OnInit)) {
if ((def.flags & NodeFlags.OnInit) &&
shouldCallInitHook(view, ViewState.CallingInit, def.nodeIndex)) {
Copy link
Contributor

@vicb vicb Nov 9, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • call it shouldCallInitLifeCycleHook ?
  • ViewState.(Calling)OnInit should rather be a hook names ?

@vicb vicb removed action: review The PR is still awaiting reviews from at least one requested reviewer state: blocked labels Nov 9, 2017
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: angular#17035
// the view will begin getting ngAfterViewInit() called until a check and
// pass is complete.
//
// This algorthim also handles recursion. Consider if E4's ngAfterViewInit()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo algorithm

// 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().
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍
Love this comment block, very helpful.

@mary-poppins
Copy link

You can preview 1018b57 at https://pr20258-1018b57.ngbuilds.io/.

@chuckjaz chuckjaz added type: bug/fix target: major This PR is targeted for the next major release action: merge The PR is ready for merge by the caretaker labels Nov 10, 2017
@jasonaden jasonaden closed this in 24cf8b3 Nov 10, 2017
gkalpak pushed a commit to gkalpak/angular that referenced this pull request Nov 14, 2017
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: angular#17035

PR Close angular#20258
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 12, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: merge The PR is ready for merge by the caretaker area: core Issues related to the framework runtime cla: yes target: major This PR is targeted for the next major release type: bug/fix
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Calling detectChanges on parent during child's ngOnChanges leads to hooks not called
4 participants