-
Notifications
You must be signed in to change notification settings - Fork 25.1k
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(upgrade): fix/improve support for lifecycle hooks #13020
fix(upgrade): fix/improve support for lifecycle hooks #13020
Conversation
BTW, this is a follow-up to #12875. |
Please update the commit comments to use "fix(upgrade):" as a prefix instead of "fix(UpgradeNg1Component):" |
0969a39
to
223870e
Compare
Rebased (to account for 9092680) and updated commit message scopes. |
@gkalpak @petebacondarwin what is the status here ? |
It needs to be reviewed 😃 |
@@ -313,6 +326,12 @@ class UpgradeNg1ComponentAdapter implements OnInit, OnChanges, DoCheck { | |||
return controller; | |||
} | |||
|
|||
private callLifecycleHook(method: LifecycleHook, context: IBindingDestination, arg?: any) { |
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.
Why is this indirection needed?
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.
Just avoiding repetition.
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.
Ah, because of the if
. OK.
const element = html(`<div><ng2></ng2></div>`); | ||
adapter.bootstrap(element, ['ng1']).ready((ref) => { | ||
expect($postLinkSpyA).toHaveBeenCalled(); | ||
expect($postLinkSpyB).toHaveBeenCalled(); |
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.
In many test cases, like this one, it's not actually verified if the hooks are called in a proper moment during the lifecycle of the component, just that they're called. I feel more thorough tests are needed.
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.
👍
There is a test for the AoT version that at least verifies the relative order of lifecycle hooks (it is not perfect, but it is better than not having it). Maybe we should port it over and also expand the current tests.
(But this is not directly related to this PR.)
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.
The order is nice but it still doesn't verify the hooks are called in proper moments (i.e. after all prerequisite operations for calling them have been finished).
But I agree it's not necessarily related to this PR.
@@ -313,6 +326,12 @@ class UpgradeNg1ComponentAdapter implements OnInit, OnChanges, DoCheck { | |||
return controller; | |||
} | |||
|
|||
private callLifecycleHook(method: LifecycleHook, context: IBindingDestination, arg?: any) { |
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.
Ah, because of the if
. OK.
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.
I think this is good enough to merge. I left a few minor comments that can be fixed up in subsequent PRs. In particular I think we should have a test that logs all the hook calls to some array so that we can easily see the order of the hooks.
bindToController: false, | ||
controllerAs: '$ctrl', | ||
controller: function() { this.$onInit = $onInitSpyB; } | ||
})) |
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.
My understanding here is that you want to test the two scenarios where $onInit
is provided on the controller instance and on the controller's prototype. So why does one have bindToController
set to true
and the other one set to false
?
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.
controller: function($scope: angular.IScope) { | ||
$scope['$onInit'] = $onInitSpy; | ||
} | ||
})) |
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.
Similar to the test for $onInit
on controllers, there seems to be two variables here: on the prototype or not and bind to controller or not. If these are independent then perhaps we need four checks?
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.
Yes, that's right: We have two independent variables (bindToControler: true/false
and prototype vs instance
). Actually, we have a third one which is scope vs controller
. So, ideally we need 8 different components tested per hook (× 5 hooks × 2 (dynamic + static upgrade) = 80 components). I cheated by testing two variables per component to save 50% of components tested.
In an ideal world, where bindToController
is indeed independent from prototype vs instance
, this should be OK. In the non-ideal world that we live in, we need the 40 extra components (or double as much faith 😛 )
ref.dispose(); | ||
}); | ||
})); | ||
it('should not call `$onDestroy()` on scope', fakeAsync(() => { |
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.
Missing carriage return here?
223870e
to
2c59924
Compare
(I just added a missing newline to fix comment, hence the rebase.) |
@gkalpak Can you rebase this to the current master to resolve conflicts? |
3e1a864
to
08d4c0f
Compare
@chuckjaz, rebased. I added one more fixup commit with the changes I made while resolving conflicts. Let me know if you prefer me to squash it. |
@gkalpak the extra commit looks good. I think you should squashi it, so that it the merge is as straightforward as possible for the caretaker. |
08d4c0f
to
a320b66
Compare
Done. |
@@ -313,6 +326,12 @@ class UpgradeNg1ComponentAdapter implements OnInit, OnChanges, DoCheck { | |||
return controller; | |||
} | |||
|
|||
private callLifecycleHook(method: LifecycleHook, context: IBindingDestination, arg?: any) { | |||
if (context && typeof context[method] === 'function') { | |||
context[method](arg); |
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 style of invocation breaks Closure advanced optimizations and we try to avoid this. Can you change this to avoid the dynamic invocation? I would prefer the code duplication than using dynamic indexing to avoid it.
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.
@chuckjaz, can you give me some more context? Do the optimizations cause the resulting code to break or does the dynamic invocation prevent the optimizations from being applied?
I would prefer the code duplication than using dynamic indexing to avoid it.
Do you mean remove the callLifecycleHook
method altogether and instead "inline" the code wherever the method was called?
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.
So it is possible that inside a Closure compiled app the $onInit
, for example, method might be renamed to something "smaller" like k
. In this case this function would break the behaviour since it would still be trying to call $onInit
.
We are not sure if this is the case, and it might be possible to get around it be annotating the function to prevent renaming.
But @chuckjaz is suggesting that it is safer simply to not do this in the first place.
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.
Done.
32afed2
to
0e34a9b
Compare
@gkalpak Can you fix the formatting break? Ensure the |
With the exception of `$onChanges()`, all lifecycle hooks in ng1 are called on the controller, regardless if it is the binding destination or not (i.e. regardless of the value of `bindToController`). This change makes `upgrade` mimic that behavior when calling lifecycle hooks. Additionally, calling the `$onInit()` hook has been moved before calling the linking functions, which also mimics the ng1 behavior.
0e34a9b
to
d67d90e
Compare
@chuckjaz, I tend to forget about that 😞 |
…mponent` Get rid of the dynamic invocation style used in `callLifecycleHook()`, which would break under Closure Compiler's advanced optimizations. Related to angular#13020 (comment).
With the exception of `$onChanges()`, all lifecycle hooks in ng1 are called on the controller, regardless if it is the binding destination or not (i.e. regardless of the value of `bindToController`). This change makes `upgrade` mimic that behavior when calling lifecycle hooks. Additionally, calling the `$onInit()` hook has been moved before calling the linking functions, which also mimics the ng1 behavior.
…mponent` (#13629) Get rid of the dynamic invocation style used in `callLifecycleHook()`, which would break under Closure Compiler's advanced optimizations. Related to #13020 (comment). PR Close #13629
…mponent` (angular#13629) Get rid of the dynamic invocation style used in `callLifecycleHook()`, which would break under Closure Compiler's advanced optimizations. Related to angular#13020 (comment). PR Close angular#13629
…mponent` (angular#13629) Get rid of the dynamic invocation style used in `callLifecycleHook()`, which would break under Closure Compiler's advanced optimizations. Related to angular#13020 (comment). PR Close angular#13629
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. |
Please check if the PR fulfills these requirements
Docs have been added / updated (for bug fixes / features)What kind of change does this PR introduce? (check one with "x")
What is the current behavior? (You can also link to an open issue here)
bindToController
).$onInit()
lifecycle hook is called after the linking phase.$postLink()
lifecycle hook is not supported.What is the new behavior?
$onChanges
, all lifecycle hooks are called on the controller instance, regardless of the value ofbindToController
(as happens in ng1).$onInit()
lifecycle hook is called before the linking phase (as happens in ng1).$postLink
lifecycle hook is supported.Does this PR introduce a breaking change? (check one with "x")
If this PR contains a breaking change, please describe the impact and migration path for existing applications:
Theoretically, this is a fix. But if an application was relying on the previous (inconsistent with ng1) behavior and timing (which they shouldn't), it might break.