-
Notifications
You must be signed in to change notification settings - Fork 27.5k
feat(shutdown): Add the ability for an app to shutdown #8005
Conversation
Thanks for the PR! Please check the items below to help us merge this faster. See the contributing docs for more information.
If you need to make changes to your pull request, you can update the commit with Thanks again for your help! |
expect(element.text()).toBe('Bootstrap me!'); | ||
}); | ||
|
||
it('should throw if trying to bootstrap an element that is not part of an app', function() { |
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 should be "to shutdown"
function rootElementProviderFactory(rootElement) { | ||
return ['$shutdownProvider', function($shutdownProvider) { | ||
$shutdownProvider.register(function() { | ||
if (rootElement.dealoc) rootElement.dealoc(); |
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.
is this jQuery specific?
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.
rootElement.dealoc()
is the jqLite specific way to recursively remove the data from an element.
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 api was removed from master recently in 9c5b407.
so the truthy branch should be removed. removeData()
will do the job.
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 no way to say "recursively remove all the data" (well, not without manually traversing the tree). In jQuery
this is possible doing rootElement.find('*').removeData()
, but not with jqLite.
Will write a manual traversing and repost the PR.
02dc2aa
to
fd2d6c0
Compare
cad9560
to
f294244
Compare
e8dc429
to
e83fab9
Compare
4dd5a20
to
998c61c
Compare
The possibility to shutdown an Angular app is still a big issue for me. Are there any plans to include this in Angular 1.4? |
@lgalfaso, what is the status of this ? |
Fixes #7578 |
This is quite old but the approach still valid. Can work on getting this up On Wednesday, June 1, 2016, Georgios Kalpakas [email protected]
|
ae83ce2
to
f77e965
Compare
Updated the PR. @gkalpak do you want to take a look at it? |
if (rootElement.dealoc) { | ||
rootElement.dealoc(); | ||
} else { | ||
rootElement.find('*').removeData(); |
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.
Not a common case for sure, but this would also "clean" inside [ng-non-bindable]
element (where theoretically could leave other angular apps).
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 there is an outer and an inner app, and the outer app is shutdown (remember that this will trigger a $destroy
event on $rootScope
), I would expect that it is reasonable for the inner app to also get destroyed.
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.
No, it shouldn't. These are independent apps, so destroying the outer, shouldn't destroy the inner. To be clear, this is what I am talking about:
<body app1>
Hello from {{ 'app 1' }}!
<div ng-non-bindable>
<div app2>
Hello from {{ 'app 2' }}!
</div>
</div>
</body>
angular.module('app1', []);
angular.module('app2', []);
angular.bootstrap(document.querySelector('[app1]'), ['app1']);
angular.bootstrap(document.querySelector('[app2]'), ['app2']);
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.
Well, this is currently not possible, as in cases like this we do not keep track of what elements belong to app1
and what elements belong to app2
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.
It is possible to ignore anything under ng-non-bindable
(because we haven't touched it anyway).
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.
Maybe something like this (which is not super efficient, but shouldn't really matter, when shutting down):
var unvisitedNodes = [$rootElement[0]];
var foundNodes = [$rootElement[0]];
var node;
while ((node = unvisitedNodes.pop())) {
var newNodes = Array.prototype.filter.call(node.children, function (child) {
return directiveNormalize(nodeName_(child)) !== 'ngNonBindable';
});
Array.prototype.push.apply(unvisitedNodes, newNodes);
Array.prototype.push.apply(foundNodes, newNodes);
}
jqLite.cleanData(foundNodes);
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.
@gkalpak if we want to support nested ng-apps in ng-non-bindable containers perhaps we should add at least basic tests verifying that it works?
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.
Perhaps. But basic tests wouldn't help much (I mean even if basic tests worked, some less basic code could still be broken). The question is whether we want to break this usecase or not (I still wouldn't).
Adds a new `$shutdown` service that can be used to shutdown an app and the `$shutdownProvider` that can be used to register tasks that need to be executed when shutting down an app
@gkalpak PTAL |
@@ -907,8 +911,7 @@ function $RootScopeProvider() { | |||
this.$$destroyed = true; | |||
|
|||
if (this === $rootScope) { | |||
//Remove handlers attached to window when $rootScope is removed | |||
$browser.$$applicationDestroyed(); | |||
$shutdown(); |
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 am not sure we need this, now that $shutdown
is a public service.
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.
@gkalpak What do you mean?
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.
Well, it's been a while, but what I think I meant that this was a poor man's $shutdown()
. Before $shutdown()
was implemented, we were special-casing $rootScope.$destroy()
to do destroy the application (because $browser.$$applicationDestroyed()
was private). But now that $shutdown()
is a public service, people wanting to destroy the app can (and should) use that (instead of destroying $rootScope
.
It doesn't seem too harmful though, so we might as well keep it to avoid the breaking change.
Generally LGTM! I have left a couple minor comments (mainly related to cleaning up
*: I just realized that bootstrapping multiple apps on the same page, could mess up the JSONP callbacks, since each app will generate the same callback IDs. |
I think that if we ignore
Oh, another part that we do not keep track from what app each callback comes... anyhow the same solution to ignore
We should be fine here as we trigger
We should be fine here as we trigger |
We already keep track of the pending requests in
Now that we have a
Good point. Missed that 😃
Are we calling |
rootElement.dealoc(); | ||
} else { | ||
rootElement.find('*').removeData(); | ||
rootElement.removeData(); |
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 not enough as it won't remove event listeners. How about:
jqLite.cleanData(rootElement.find('*'));
jqLite.cleanData(rootElement);
or something analogous to @gkalpak's code but using cleanData
if we want to not break ng-non-bindable
-contained AngularJS apps.
This, BTW, made me notice that jqLite's implementation of removeData
is really broken. It should only remove user-set data and not touch the event handlers storage at all. I reported #15869 to fix that in 1.7.
}, delay || 0); | ||
pendingDeferIds[timeoutId] = true; | ||
} else { | ||
timeoutId = 0; |
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.
What's the purpose of this line?
}); | ||
self.interval.repeatFns.sort(function(a,b) { return a.nextTime - b.nextTime;}); | ||
|
||
return self.interval.nextRepeatId++; |
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.
Please separate this into two instructions; ease of reading is more important than being shorter, especially in mocks code.
var task = self.interval.repeatFns[0]; | ||
task.fn(); | ||
task.nextTime += task.delay; | ||
self.interval.repeatFns.sort(function(a,b) { return a.nextTime - b.nextTime;}); |
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.
Is this necessary? The queue is sorted during callback registration already.
@@ -1671,6 +1671,110 @@ describe('angular', function() { | |||
}); | |||
}); | |||
|
|||
describe('shutdown', function() { |
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.
We should test that event handlers are removed as well (current code doesn't do that when jQuery is used).
This is a pretty big change which has a potential of introducing regressions. Considering that and taking into account the fact that we're getting close to the LTS phase when only a few kinds of issues get fixed (like security ones) we've decided to not pursue it further. |
Adds a new
$shutdown
service that can be used to shutdown an app and the$shutdownProvider
that can be used to register tasks that need to be executed when shutting down an app.Added tasks for
$rootElement
,$rootScope
and$browser
to be able to shutdown an appRefactor
$interval
so it delegates to$browser
just like$timeout
does