-
Notifications
You must be signed in to change notification settings - Fork 27.4k
feat($compile): add $onDestroy directive lifecycle hook #14127
Conversation
Introduce a destroy hook for directives which follows the ng2 component lifecycle Closes angular#14020
@@ -2424,6 +2426,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { | |||
if (isFunction(controller.instance.$onInit)) { | |||
controller.instance.$onInit(); | |||
} | |||
if (isFunction(controller.instance.$onDestroy)) { | |||
scope.$on('$destroy', controller.instance.$onDestroy); |
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.
Two main comments here (was actually implementing this just now until I saw this):
- This doesn't allow late-bound
$onDestroy
methods to be added. If it's not there at the time of controller instantiation, the lifecycle hook will not function. Does that follow expectations, or do we need to be checking this at the time of destruction? - The
this
binding in the$onDestroy
method is going to be incorrect here due to the manner in which you're passing$onDestroy
. I see that you copied the unit tests from the one above it as I had done, but removed thecheck
call. You should re-introduce that portion of the test since it would have failed on this point
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.
Thanks for your input. I fixed you second point.
Bind $onDestroy handler to the controller instance Closes angular#14020
@dcherman Yes, we should allow late-bound $onDestroy methods. We could attach scope.$on('$destroy') listeners for every controller and then check if the $onDestroy method is there. But it does not feel right to attach this listener on every controller. What do you think? |
@@ -2424,6 +2426,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { | |||
if (isFunction(controller.instance.$onInit)) { | |||
controller.instance.$onInit(); | |||
} | |||
if (isFunction(controller.instance.$onDestroy)) { | |||
scope.$on('$destroy', controller.instance.$onDestroy.bind(controller.instance)); |
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 problem with this is that scope
might be a parent scope that is not directly related to the element.
Maybe we could use $element.on('$destroy', ...)
instead (I haven't given it much thought, though).
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.
Ok. The element.$on solution sounds better to me because we care about the destruction of the component/directive in specific, not about the scope. Thanks for the insight, will create a new solution.
Call $onDestroy when element is removed, possibility to set $onDestroy method after controller initialization Closes angular#14020
forEach(elementControllers, function(controller) { | ||
if (isFunction(controller.instance.$onInit)) { | ||
controller.instance.$onInit(); | ||
} | ||
|
||
$element.on('$destroy', 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.
The $destroy
event won't function properly when you have a transclude: "element"
directive since events are not fired on the comment node that becomes $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.
So it should be implemented using $scope.$on('$destroy', ... ?
Figuring out exactly where to run this logic is trickier than it looks. We only have two existing hooks that can be used for something like this which are the |
Edit: This idea is pretty much invalid/derp. The new scope's lifecycle would match the parent's, making it pointless to have created it in the first place. How common do we feel that So I was thinking about this for a little bit this morning, and although the
I have no data to back up this assertion (at all), but I feel like it may not be that common to have a controller on a directive that doesn't define either an isolate or new scope, so this codepath may be hit less frequently than we might imagine. The new |
The more I think about it, the more it doesn't make sense to try to cover all corner-cases and end up with a complicated bloat (that will have little resemblance to ng2). Since the main purpose (or at least one of the main purposes) of the directive lifecycle hooks is to enable structuring your directive in a way that is close to the ng2-way (and thus facilitate the migration), it is reasonable to only offer the In ng2 all directives have sort of their own scope/context (the component/directive class). So, by not providing the This way, we will avoid lots of irrelevant corner cases and we'll be able to implement it using WDYT ? /cc @petebacondarwin |
I start to think that may be components are not directives:
So, may be hooks can only be applied from component constructor or only to components. In such case no need for forEach controller, less overhead in directives, ... (may be all of this can be achieved by providing pre/post-link functions just for components). |
I think we should implement it via |
What I mean about components "are not" directives is may be that something such simple as changing return {
controller: controller,
controllerAs: identifierForController(options.controller) || options.controllerAs || '$ctrl',
template: makeInjectable(template),
templateUrl: makeInjectable(options.templateUrl),
transclude: options.transclude,
scope: {},
bindToController: options.bindings || {},
restrict: 'E',
require: !options.require && [name] || [name].concat(options.require),
link: {
pre: function(scope, element, attrs, controllers) {
if (isFunction(controllers[0].$onInit)) {
controllers[0].$onInit();
}
},
post: function(scope, element, attrs, controllers) {
scope.$on('$destroy', function() {
if (isFunction(controllers[0].$onDestroy)) {
controllers[0].$onDestroy();
}
});
// placed after registering to avoid not firing destroy if afterViewInit throws an exception (and save a non-optimizable try-catch)
if (isFunction(controllers[0].$afterViewInit)) {
controllers[0].$afterViewInit();
}
},
}; |
@drpicox - I like what you are saying but I feel that developers ought to expect the same hooks to be called even if they create component directives using the |
Yes, you are right. I was just trying to give another vision, but right, it is not the best one. |
This change adds in the following new lifecycle hooks, which map in some way to those in Angular 2: * `$onChanges(changesObj)` - Called whenever one-way bindings are updated. The `changesObj` is a hash whose keys are the names of the bound properties that have changed, and the values are an object of the form `{ currentValue: ..., previousValue: ... }`. Use this hook to trigger updates within a component such as cloning the bound value to prevent accidental mutation of the outer value. * `$onDestroy` - Called on a controller when its containing scope is destroyed. Use this hook for releasing external resources, watches and event handlers. * `$afterViewInit` - Called after this controller's element and its children been linked. Similar to the post-link function this hook can be used to set up DOM event handlers and do direct DOM manipulation. Note that child elements that contain `templateUrl` directives will not have been compiled and linked since they are waiting for their template to load asynchronously and their own compilation and linking has been suspended until that occurs. Closes angular#14127 Closes angular#14030 Closes angular#14020 Closes angular#13991
This change adds in the following new lifecycle hooks, which map in some way to those in Angular 2: * `$onChanges(changesObj)` - Called whenever one-way bindings are updated. The `changesObj` is a hash whose keys are the names of the bound properties that have changed, and the values are an object of the form `{ currentValue: ..., previousValue: ... }`. Use this hook to trigger updates within a component such as cloning the bound value to prevent accidental mutation of the outer value. * `$onDestroy` - Called on a controller when its containing scope is destroyed. Use this hook for releasing external resources, watches and event handlers. * `$afterViewInit` - Called after this controller's element and its children been linked. Similar to the post-link function this hook can be used to set up DOM event handlers and do direct DOM manipulation. Note that child elements that contain `templateUrl` directives will not have been compiled and linked since they are waiting for their template to load asynchronously and their own compilation and linking has been suspended until that occurs. Closes angular#14127 Closes angular#14030 Closes angular#14020 Closes angular#13991 Closes angular#14302
This change adds in the following new lifecycle hooks, which map in some way to those in Angular 2: * `$onChanges(changesObj)` - Called whenever one-way bindings are updated. The `changesObj` is a hash whose keys are the names of the bound properties that have changed, and the values are an object of the form `{ currentValue: ..., previousValue: ... }`. Use this hook to trigger updates within a component such as cloning the bound value to prevent accidental mutation of the outer value. * `$onDestroy` - Called on a controller when its containing scope is destroyed. Use this hook for releasing external resources, watches and event handlers. * `$afterViewInit` - Called after this controller's element and its children been linked. Similar to the post-link function this hook can be used to set up DOM event handlers and do direct DOM manipulation. Note that child elements that contain `templateUrl` directives will not have been compiled and linked since they are waiting for their template to load asynchronously and their own compilation and linking has been suspended until that occurs. Closes angular#14127 Closes angular#14030 Closes angular#14020 Closes angular#13991 Closes angular#14302
This change adds in the following new lifecycle hooks, which map in some way to those in Angular 2: * `$onChanges(changesObj)` - Called whenever one-way bindings are updated. The `changesObj` is a hash whose keys are the names of the bound properties that have changed, and the values are an object of the form `{ currentValue: ..., previousValue: ... }`. Use this hook to trigger updates within a component such as cloning the bound value to prevent accidental mutation of the outer value. * `$onDestroy` - Called on a controller when its containing scope is destroyed. Use this hook for releasing external resources, watches and event handlers. * `$afterViewInit` - Called after this controller's element and its children been linked. Similar to the post-link function this hook can be used to set up DOM event handlers and do direct DOM manipulation. Note that child elements that contain `templateUrl` directives will not have been compiled and linked since they are waiting for their template to load asynchronously and their own compilation and linking has been suspended until that occurs. Closes angular#14127 Closes angular#14030 Closes angular#14020 Closes angular#13991 Closes angular#14302
This change adds in the following new lifecycle hooks, which map in some way to those in Angular 2: * `$onChanges(changesObj)` - Called whenever one-way bindings are updated. The `changesObj` is a hash whose keys are the names of the bound properties that have changed, and the values are an object of the form `{ currentValue: ..., previousValue: ... }`. Use this hook to trigger updates within a component such as cloning the bound value to prevent accidental mutation of the outer value. * `$onDestroy` - Called on a controller when its containing scope is destroyed. Use this hook for releasing external resources, watches and event handlers. * `$postLink` - Called after this controller's element and its children been linked. Similar to the post-link function this hook can be used to set up DOM event handlers and do direct DOM manipulation. Note that child elements that contain `templateUrl` directives will not have been compiled and linked since they are waiting for their template to load asynchronously and their own compilation and linking has been suspended until that occurs. Closes angular#14127 Closes angular#14030 Closes angular#14020 Closes angular#13991 Closes angular#14302
This change adds in the following new lifecycle hooks, which map in some way to those in Angular 2: * `$onChanges(changesObj)` - Called whenever one-way bindings are updated. The `changesObj` is a hash whose keys are the names of the bound properties that have changed, and the values are an object of the form `{ currentValue: ..., previousValue: ... }`. Use this hook to trigger updates within a component such as cloning the bound value to prevent accidental mutation of the outer value. * `$onDestroy` - Called on a controller when its containing scope is destroyed. Use this hook for releasing external resources, watches and event handlers. * `$postLink` - Called after this controller's element and its children been linked. Similar to the post-link function this hook can be used to set up DOM event handlers and do direct DOM manipulation. Note that child elements that contain `templateUrl` directives will not have been compiled and linked since they are waiting for their template to load asynchronously and their own compilation and linking has been suspended until that occurs. Closes angular#14127 Closes angular#14030 Closes angular#14020 Closes angular#13991 Closes angular#14302
This change adds in the following new lifecycle hooks, which map in some way to those in Angular 2: * `$onChanges(changesObj)` - Called whenever one-way bindings are updated. The `changesObj` is a hash whose keys are the names of the bound properties that have changed, and the values are an object of the form `{ currentValue: ..., previousValue: ... }`. Use this hook to trigger updates within a component such as cloning the bound value to prevent accidental mutation of the outer value. * `$onDestroy` - Called on a controller when its containing scope is destroyed. Use this hook for releasing external resources, watches and event handlers. * `$postLink` - Called after this controller's element and its children been linked. Similar to the post-link function this hook can be used to set up DOM event handlers and do direct DOM manipulation. Note that child elements that contain `templateUrl` directives will not have been compiled and linked since they are waiting for their template to load asynchronously and their own compilation and linking has been suspended until that occurs. Closes angular#14127 Closes angular#14030 Closes angular#14020 Closes angular#13991 Closes angular#14302
This change adds in the following new lifecycle hooks, which map in some way to those in Angular 2: * `$onChanges(changesObj)` - Called whenever one-way bindings are updated. The `changesObj` is a hash whose keys are the names of the bound properties that have changed, and the values are an object of the form `{ currentValue: ..., previousValue: ... }`. Use this hook to trigger updates within a component such as cloning the bound value to prevent accidental mutation of the outer value. * `$onDestroy` - Called on a controller when its containing scope is destroyed. Use this hook for releasing external resources, watches and event handlers. * `$postLink` - Called after this controller's element and its children been linked. Similar to the post-link function this hook can be used to set up DOM event handlers and do direct DOM manipulation. Note that child elements that contain `templateUrl` directives will not have been compiled and linked since they are waiting for their template to load asynchronously and their own compilation and linking has been suspended until that occurs. Closes angular#14127 Closes angular#14030 Closes angular#14020 Closes angular#13991 Closes angular#14302
This change adds in the following new lifecycle hooks, which map in some way to those in Angular 2: * `$onChanges(changesObj)` - Called whenever one-way bindings are updated. The `changesObj` is a hash whose keys are the names of the bound properties that have changed, and the values are an object of the form `{ currentValue: ..., previousValue: ... }`. Use this hook to trigger updates within a component such as cloning the bound value to prevent accidental mutation of the outer value. * `$onDestroy` - Called on a controller when its containing scope is destroyed. Use this hook for releasing external resources, watches and event handlers. * `$postLink` - Called after this controller's element and its children been linked. Similar to the post-link function this hook can be used to set up DOM event handlers and do direct DOM manipulation. Note that child elements that contain `templateUrl` directives will not have been compiled and linked since they are waiting for their template to load asynchronously and their own compilation and linking has been suspended until that occurs. Closes angular#14127 Closes angular#14030 Closes angular#14020 Closes angular#13991 Closes angular#14302
This change adds in the following new lifecycle hooks, which map in some way to those in Angular 2: * `$onChanges(changesObj)` - Called whenever one-way bindings are updated. The `changesObj` is a hash whose keys are the names of the bound properties that have changed, and the values are an object of the form `{ currentValue: ..., previousValue: ... }`. Use this hook to trigger updates within a component such as cloning the bound value to prevent accidental mutation of the outer value. * `$onDestroy` - Called on a controller when its containing scope is destroyed. Use this hook for releasing external resources, watches and event handlers. * `$postLink` - Called after this controller's element and its children been linked. Similar to the post-link function this hook can be used to set up DOM event handlers and do direct DOM manipulation. Note that child elements that contain `templateUrl` directives will not have been compiled and linked since they are waiting for their template to load asynchronously and their own compilation and linking has been suspended until that occurs. Closes angular#14127 Closes angular#14030 Closes angular#14020 Closes angular#13991 Closes angular#14302
This change adds in the following new lifecycle hooks, which map in some way to those in Angular 2: * `$onChanges(changesObj)` - Called whenever one-way bindings are updated. The `changesObj` is a hash whose keys are the names of the bound properties that have changed, and the values are an object of the form `{ currentValue: ..., previousValue: ... }`. Use this hook to trigger updates within a component such as cloning the bound value to prevent accidental mutation of the outer value. * `$onDestroy` - Called on a controller when its containing scope is destroyed. Use this hook for releasing external resources, watches and event handlers. * `$postLink` - Called after this controller's element and its children been linked. Similar to the post-link function this hook can be used to set up DOM event handlers and do direct DOM manipulation. Note that child elements that contain `templateUrl` directives will not have been compiled and linked since they are waiting for their template to load asynchronously and their own compilation and linking has been suspended until that occurs. Closes angular#14127 Closes angular#14030 Closes angular#14020 Closes angular#13991 Closes angular#14302
This change adds in the following new lifecycle hooks, which map in some way to those in Angular 2: * `$onChanges(changesObj)` - Called whenever one-way bindings are updated. The `changesObj` is a hash whose keys are the names of the bound properties that have changed, and the values are an object of the form `{ currentValue: ..., previousValue: ... }`. Use this hook to trigger updates within a component such as cloning the bound value to prevent accidental mutation of the outer value. * `$onDestroy` - Called on a controller when its containing scope is destroyed. Use this hook for releasing external resources, watches and event handlers. * `$postLink` - Called after this controller's element and its children been linked. Similar to the post-link function this hook can be used to set up DOM event handlers and do direct DOM manipulation. Note that child elements that contain `templateUrl` directives will not have been compiled and linked since they are waiting for their template to load asynchronously and their own compilation and linking has been suspended until that occurs. Closes angular#14127 Closes angular#14030 Closes angular#14020 Closes angular#13991 Closes angular#14302
This change adds in the following new lifecycle hooks, which map in some way to those in Angular 2: * `$onChanges(changesObj)` - Called whenever one-way bindings are updated. The `changesObj` is a hash whose keys are the names of the bound properties that have changed, and the values are an object of the form `{ currentValue: ..., previousValue: ... }`. Use this hook to trigger updates within a component such as cloning the bound value to prevent accidental mutation of the outer value. * `$onDestroy` - Called on a controller when its containing scope is destroyed. Use this hook for releasing external resources, watches and event handlers. * `$postLink` - Called after this controller's element and its children been linked. Similar to the post-link function this hook can be used to set up DOM event handlers and do direct DOM manipulation. Note that child elements that contain `templateUrl` directives will not have been compiled and linked since they are waiting for their template to load asynchronously and their own compilation and linking has been suspended until that occurs. Closes #14127 Closes #14030 Closes #14020 Closes #13991 Closes #14302
This PR introduces a new $onDestroy lifecycle hook for directives. This hook matches the destroy hook that's present in Angular 2.
Closes #14020