-
Notifications
You must be signed in to change notification settings - Fork 26.2k
fix(upgrade): properly destroy upgraded component elements and descendants #26209
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
Conversation
53267b4
to
528fe26
Compare
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.
Nice catch 💯 I left a couple of comments, but overall LGTM 👍
IIRC this change also affects the dynamic version. If so, can you please add a test there as well?
@@ -287,4 +287,7 @@ export const resumeBootstrap: typeof angular.resumeBootstrap = () => angular.res | |||
|
|||
export const getTestability: typeof angular.getTestability = e => angular.getTestability(e); | |||
|
|||
export const cleanData: (l: NodeList | Node[]) => void = (nodes) => |
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 expose it as angular.cleanData()
? This is not consistent with the actual API 😕
We should put it on angular.element
(and type it appropriately).
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.
Put it on the element
method exported in this file? I guess that would make it look more like the real thing being wrapped by this file...
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.
Note that when doing import * as angular
from this file it is already inconsistent with the actual API, it has things like setAngularJSGlobal
on it etc. I was assuming it more like a bunch of helpers...
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.
Having some extra APIs is one thing. Having the same helpers on different location (e.g. angular.cleanData()
vs angular.element.cleanData()
) is another story.
I agree that this file is already a little confusing. (I am not even sure why all the redirection tbh 😁)
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 to clarify, you mean changing this line to element.cleanData = ...
?
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 what I had in mind:
let angular: {
bootstrap: (e: Element, modules: (string | IInjectable)[], config?: IAngularBootstrapConfig) =>
IInjectorService,
module: (prefix: string, dependencies?: string[]) => IModule,
- element: (e: string | Element | Document | IAugmentedJQuery) => IAugmentedJQuery,
+ element: {
+ (e: string | Element | Document | IAugmentedJQuery): IAugmentedJQuery;
+ cleanData: (nodes: Node[] | NodeList[]) => void;
+ },
version: {major: number},
resumeBootstrap: () => void,
getTestability: (e: Element) => ITestabilityService
} = <any>{
bootstrap: noNg,
module: noNg,
- element: noNg,
+ element: Object.assign(() => noNg(), {cleanData: noNg}),
version: undefined,
resumeBootstrap: noNg,
getTestability: noNg
};
...
-export const element: typeof angular.element = e => angular.element(e);
+export const element: typeof angular.element = Object.assign(e => angular.element(e), {
+ cleanData: nodes => angular.element.cleanData(nodes),
+});
@@ -124,7 +124,8 @@ export class UpgradeHelper { | |||
controllerInstance.$onDestroy(); | |||
} | |||
$scope.$destroy(); | |||
this.$element.triggerHandler !('$destroy'); | |||
angular.cleanData([this.element]); | |||
angular.cleanData(this.element.querySelectorAll('*')); |
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 suggest we implement a private jqLiteDealoc()
helper and only call this.jqLiteDealoc(this.element)
here.
The helper should follow the AngularJS implementation as close as possible (e.g. have a check similar to jsLiteAcceptsData() and if (element.querySelectorAll)), but it can omit features that we don't use (such as onlyDescendants
).
Also, please add a link to the AngularJS implementation for reference.
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.
Note that the jQuery acceptsData is slightly different :/
I think this needs to be compatible with both? Although don't we already know that this.element
is an element? Do we really need to do acceptsData
on 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.
Actually... since we know it's an Element
isn't the existing solution fine?
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 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.
SGTM. Definitely add that comment though 😁
@@ -3694,6 +3694,206 @@ withEachNg1Version(() => { | |||
}); | |||
})); | |||
|
|||
it('should emit `$destroy` on `$element` descendants', async(() => { |
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 it is better to merge this with the above test.
I've added a commit trying to add |
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 reason the tests fail is that in upgrade/dynamic
the controller (under certain circumstances) can be instantiated before the directive's template has been compiled. Thus $element.contents()
inside the controller constructor is not what you think it is 😱
(Not sure why this is that way, but it is probably better not to alter upgrade/dynamic
's behavior so drastically at this point.)
You can use the controller's $onInit()
hook instead of the constructor.
export const element: typeof angular.element = Object.assign( | ||
(e: string | Element | Document | IAugmentedJQuery) => angular.element(e), | ||
{ | ||
cleanData: (nodes: NodeList | Node[]) => angular.element.cleanData(nodes) |
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: The order of types is different than in angular
's type above (Node[] | NodeList
vs NodeList | Node[]
) 😁
@@ -6,12 +6,13 @@ | |||
* found in the LICENSE file at https://angular.io/license | |||
*/ | |||
|
|||
import {ChangeDetectorRef, Component, EventEmitter, Input, NO_ERRORS_SCHEMA, NgModule, NgModuleFactory, NgZone, OnChanges, OnDestroy, Output, SimpleChange, SimpleChanges, Testability, destroyPlatform, forwardRef} from '@angular/core'; | |||
import {ChangeDetectorRef, Component, Directive, EventEmitter, Input, NO_ERRORS_SCHEMA, NgModule, NgModuleFactory, NgZone, OnChanges, OnDestroy, Output, SimpleChange, SimpleChanges, Testability, destroyPlatform, forwardRef} from '@angular/core'; |
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.
Unused?
d32c85c
to
f9b55a6
Compare
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.
LGTM
controller: function($element: angular.IAugmentedJQuery) { | ||
$element.on !('$destroy', elementDestroyListener); | ||
controller: class { | ||
constructor(private $element: angular.IAugmentedJQuery) {} $onInit() { |
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.
Nice job, clang-format 👌
🤢 🤢 🤢
f9b55a6
to
e383c49
Compare
e383c49
to
10bf47e
Compare
An integration test is failing, twice in a row now. Seems like it is randomly not picking up key presses (picked up a different number of them each run). However that test doesn't use ng-upgrade from what I can see, so I assume it's unrelated... |
@jbedard: You just need to keep pushing that button 😛 It's all green now. Note to caretaker: Needs g3 sync. |
…d descendants (angular#26209)" This reverts commit 5a31bde.
…d descendants (angular#26209)" This reverts commit 912f3d1. Revert is needed due to compilation failures due to this PR inside Google.
Unfortunately this continues to fail in |
Internally in Google we get the same failure as on the Again, due to a slightly older version of TypeScript. |
I'll fix it up 😞 |
So there's good news and bad news. 👍 The good news is that everyone that needs to sign a CLA (the pull request submitter and all commit authors) have done so. Everything is all good there. 😕 The bad news is that it appears that one or more commits were authored or co-authored by someone other than the pull request submitter. We need to confirm that all authors are ok with their commits being contributed to this project. Please have them confirm that here in the pull request. Note to project maintainer: This is a terminal state, meaning the |
The CI failures are unrelated to this PR 😞 |
A Googler has manually verified that the CLAs look good. (Googler, please make sure the reason for overriding the CLA status is clearly documented in these comments.) |
Fingers crossed it stays in this time 😁 |
…d descendants (angular#26209)" This reverts commit 912f3d1. Revert is needed due to compilation failures due to this PR inside Google.
…d descendants (angular#26209)" This reverts commit 5a31bde.
…d descendants (angular#26209)" This reverts commit 912f3d1. Revert is needed due to compilation failures due to this PR inside Google.
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. |
Fixes #26208
PR Type
What is the current behavior?
Upgraded AngularJS dom elements are not destroyed properly (via jQuery) causing events+data to not be cleaned up. Child elements do not trigger the
$destroy
dom event.Issue Number: #26208
What is the new behavior?
Upgraded AngularJS dom elements, along with descendants, have all events+data cleaned and
$destroy
triggered.Does this PR introduce a breaking change?