Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Implement lifecycle hooks for directive/component controllers (ng2-style) #14030

Closed
5 of 9 tasks
gkalpak opened this issue Feb 13, 2016 · 14 comments · Fixed by #14302
Closed
5 of 9 tasks

Implement lifecycle hooks for directive/component controllers (ng2-style) #14030

gkalpak opened this issue Feb 13, 2016 · 14 comments · Fixed by #14302

Comments

@gkalpak
Copy link
Member

gkalpak commented Feb 13, 2016

I open this issue in order to track/discuss the implementation of lifecucle hooks to directive/component controllers.

Context

In our attempt to improve the upgrade path to Angular 2, it makes sense to allow developers structure their directive/component controllers is a way that is as easy as possible to migrate them to Angular 2 (ideally, there should be no modification of the controllers code necessary).
As part of this attempt, it will be useful to implement some of the lifecycle hooks available in Angular 2. (Obviously, it isn't necessary (or possible) to implement all of them, due to differences between ng1/ng2 internals.)

Lifecycle Hooks

UPDATE:
Since we can't match the semantics of AfterView/ContentInit closely enough, we implemented a $postLink hook instead, which is close (but not the same) and fits ng1 semantics better.

@thorn0
Copy link
Contributor

thorn0 commented Mar 8, 2016

Why is OnChanges not applicable?

@gkalpak
Copy link
Member Author

gkalpak commented Mar 8, 2016

I'm not sure what I was thinking at the time. Maybe because it's tied to Inputs/Outputs and ng1 doesn't have the same semantics. It would probably be reasonably feasible to implement for '@' and '<' as inputs and '&' as outputs (with a small overhead), but with two-way binding it gets really muddy.

But I can definitely be convinced otherwise. One of the main purposes of opening the issue was to get the discussion going.

@thorn0
Copy link
Contributor

thorn0 commented Mar 8, 2016

I'm not sure if OnChanges works with outputs at all. Even though the docs state otherwise:

ngOnChanges - called when an input or output binding value changes

petebacondarwin added a commit to petebacondarwin/angular.js that referenced this issue Mar 22, 2016
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
petebacondarwin added a commit to petebacondarwin/angular.js that referenced this issue Mar 23, 2016
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
petebacondarwin added a commit to petebacondarwin/angular.js that referenced this issue Mar 23, 2016
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
petebacondarwin added a commit to petebacondarwin/angular.js that referenced this issue Mar 23, 2016
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
petebacondarwin added a commit to petebacondarwin/angular.js that referenced this issue Mar 23, 2016
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
petebacondarwin added a commit to petebacondarwin/angular.js that referenced this issue Mar 23, 2016
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
petebacondarwin added a commit to petebacondarwin/angular.js that referenced this issue Mar 23, 2016
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
petebacondarwin added a commit to petebacondarwin/angular.js that referenced this issue Mar 23, 2016
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
petebacondarwin added a commit to petebacondarwin/angular.js that referenced this issue Mar 23, 2016
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
petebacondarwin added a commit to petebacondarwin/angular.js that referenced this issue Mar 24, 2016
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
petebacondarwin added a commit to petebacondarwin/angular.js that referenced this issue Mar 24, 2016
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
petebacondarwin added a commit to petebacondarwin/angular.js that referenced this issue Mar 25, 2016
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
petebacondarwin added a commit that referenced this issue Mar 25, 2016
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
@mikeerickson
Copy link

I am unable to get $onChanges to fire regardless what I try. To further complicate matters, the documentation is VERY vague and there are basically NO examples, etc. online (I am trying to get it working with ES6 modules).

If anybody has a working example (with ES6) which you wish to share, I would be greatly appreciative.

Thanks in advance...

@petebacondarwin
Copy link
Contributor

I don't have ES6 examples to hand - but here is a project that uses $onChanges effectively: github.com/petebacondarwin/ng1-component-demo

@adrianolsk
Copy link

@mikeerickson

Looks like $onChanges only watch for changes on simple variables, so in objects it doesn´t do a deep check of the properties it only check if the object reference change.

so if you change the object using angular.copy it will trigger the $onChanges
or you can inject the $scope and use the $watch function so you could pass the parameter to deep check.

@petebacondarwin
Copy link
Contributor

@adrianolsk - absolutely. We made a conscious decision not to watch objects deeply for $onChanges. It would add an unwanted performance penalty if your app didn't actually mutate objects, especially if they are large.
Also, what it means for an object to change is sometimes subtle and not that easy to put into a single general algorithm.
Finally, it is worth noting that this is how Angular 2 works as well.

As you suggest, if you really care about tracking deep property changes on your inputs then rather than using $onChanges you should use $scope.$watch(..., true) where the true indicates that you want to check every property deeply, or use $scope.$watchCollection(...), which will watch for changes only to the first layer of the object/array.

@samal-rasmussen
Copy link

Yeah like @adrianolsk mentions, it's probably easiest to just do a angular.copy to create a new object.

Won't trigger $onChange if you're binding on complicatedObject:
complicatedObject.someProperty = 'new value';

But this will:
complicatedObject.someProperty = 'new value';
complicatedObject = angular.copy(complicatedObject);

@mazzur
Copy link

mazzur commented Jun 20, 2016

@gkalpak @adrianolsk Can you please explain why was doCheck considered not applicable? I think $doCheck hook could be used for cases like deep watching object properties supplementing $onChanges hook (as it is in angular 2). We encountered the need for it in a quite huge project when migrating to 1.5. Still using $scope seems counterproductive if one is planning to eventually migrate to angular 2, and the workaround proposed by @samal84 seems to be a bit of an overhead.

@petebacondarwin
Copy link
Contributor

There is actually a PR discussing this feature - #14656 - let's move the discussion there.

@kirinmurphy
Copy link

Was struggling with this one too. Came up with an even simpler approach that avoids the overhead of angular.copy() and the step backwards to using $scope. Just don't deep watch the object. Add a one way binding directly to the nested property you want to observe in addition to it's parent $onChanges then works exactly as intended.

@gkalpak
Copy link
Member Author

gkalpak commented Dec 8, 2016

BTW, we now support $doCheck() if anyone's intrested 😃

@bernardoadc
Copy link

@kirinmurphy this seems to be the best approach indeed. However, complex objects with nested objects won't work either. You would basically have to break the object in every possible piece that you want to update with. This seems counter productive.

PS: I think from a logical point of view avoiding the extremes is the better option - not just 1 object holding all properties of the controller, nor break it all apart, but 'chunks' of 'main' properties, that associate with general actions/updates from the app (a button click, UX events, etc).

@samal-rasmussen
Copy link

@khullah you can deep watch objects if you really want to: http://stackoverflow.com/questions/14712089/how-to-deep-watch-an-array-in-angularjs

Just be mindful of the performance hit.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.