-
Notifications
You must be signed in to change notification settings - Fork 25.4k
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
Conversation
You can preview 75fd092 at https://pr20258-75fd092.ngbuilds.io/. |
packages/core/src/view/types.ts
Outdated
// lifecycle methods. | ||
export function checkCycle(view: ViewData, cycle: ViewState): boolean { | ||
const state = view.state; | ||
if ((state & ViewState.InitStateMask) === ((cycle - 1) & ViewState.InitStateMask)) { |
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
Changes need to land in G3 before this can land. |
You can preview ba73309 at https://pr20258-ba73309.ngbuilds.io/. |
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++); |
There was a problem hiding this comment.
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 ?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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++); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same comment here
packages/core/src/view/types.ts
Outdated
// 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. |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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])
You can preview 2e21554 at https://pr20258-2e21554.ngbuilds.io/. |
packages/core/src/view/provider.ts
Outdated
@@ -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)) { |
There was a problem hiding this comment.
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 ?
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() |
There was a problem hiding this comment.
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(). |
There was a problem hiding this comment.
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.
You can preview 1018b57 at https://pr20258-1018b57.ngbuilds.io/. |
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
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
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, callingdetectChanges()
in a way that causes duplicate change detection (such as a
child component causing a parent to call
detectChanges()
on itsown
ChangeDetectorRef
, will no longer prevent changengOnInit
,ngAfterContentInit
andngAfterViewInit
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?
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
andngAfterViewInit
, are called once an only once.Does this PR introduce a breaking change?