Angular.js: Controllers hooks order ($onChanges called before $onInit)

Created on 16 Dec 2016  ·  9Comments  ·  Source: angular/angular.js

Do you want to request a feature or report a bug?
I dont know if it's wanted or if it's a bug

What is the current behavior?
First $onChanges call is done before the $onInit one.
With angular 1.6 default configuration (preassign = false), it's prefearable to initialize controller states in the $onInit function, given the bindings are not accessible yet in the constructor.

What is the expected behavior?
I think it would be more logical to call $onInit first.
Angular shouldnt do anything while the controller is not fully initialized.

What is the motivation / use case for changing the behavior?
If we use objects that are supposed to be created in the $onInit function in $onChanges function; we'll have an error in the first call, because the controller has not been initialized yet.
A solution could be to initialize those objects inside the constructor, but we'll initialize the controller in two different places...

Which versions of Angular, and which browser / OS are affected by this issue? Did this work in previous versions of Angular? Please also test with the latest stable and snapshot (https://code.angularjs.org/snapshot/) versions.
angular 1.6 version

works as expected

Most helpful comment

This is intended (mainly in order to match the Angular 2+ behavior). In case you have missed it, you can check whether it is the first (pre-$onInit) call of $onChanges, by checking the return value of the isFirstChange() method of any of the SimpleChange objects:

{
  ...
  bindings: {foo: '<'},
  controller: function SomeController() {
    this.$onChanges = function(changes) {
      if (changes.foo.isFirstChange()) {
        // `$onInit()` has not been called yet...
      }
    };
  }
}

#

I admit that my first reaction was the same. From a mental model perspective it is easier to think $onInit() is the first thing that happens in the controller; then $onChanges(), $onChanges(), $onChanges() and finally $onDestroy().

But if you think about it, the bindings need to be evaluated and assigned to the controller instance before calling $onInit. And by doing so, a change in the (previously undefined) values is detected, which in turn needs to be reported via $onChanges.

Closing, since this is working as expected (or at least as intended :wink:).

All 9 comments

This is intended (mainly in order to match the Angular 2+ behavior). In case you have missed it, you can check whether it is the first (pre-$onInit) call of $onChanges, by checking the return value of the isFirstChange() method of any of the SimpleChange objects:

{
  ...
  bindings: {foo: '<'},
  controller: function SomeController() {
    this.$onChanges = function(changes) {
      if (changes.foo.isFirstChange()) {
        // `$onInit()` has not been called yet...
      }
    };
  }
}

#

I admit that my first reaction was the same. From a mental model perspective it is easier to think $onInit() is the first thing that happens in the controller; then $onChanges(), $onChanges(), $onChanges() and finally $onDestroy().

But if you think about it, the bindings need to be evaluated and assigned to the controller instance before calling $onInit. And by doing so, a change in the (previously undefined) values is detected, which in turn needs to be reported via $onChanges.

Closing, since this is working as expected (or at least as intended :wink:).

Thank you very much for you answer, it's clearer now :)

I understand this is intentional, how about if we register definition of $onChanges() function inside $onInit().

@bharatpatil, it should work in AngularJS (1.x.) but won't in Angular (2+).

if (changes.foo.isFirstChange()) {
// $onInit() has not been called yet...
}

@gkalpak I think this argument is wrong. binding.isFirstChange() method only guarantees that related binding was called the first time. Think a binding which is initialized after a service call takes too long time. In this situation controller.$onInit method should be called before controller.$onChanges method. Am I wrong?

A binding always has a value at the beginning (could be undefined), which is always different that its pre-initialized value. Thus, $onChanges() will always be called with changes.foo before calling $onInit().

It does not make logical sense that a binding can have a value before it has been initialised. That's the dictionary definition of the word initialise (set initial values). You cannot change something if it didn't have an initial value. I think the naming is all wrong here and unnatural.

I can't remember a situation where I wasn't forced to write something like this code:

$onChanges(changesObj: { [index: string]: angular.IChangesObject; })  {
   if (!this.isInitialized) return;
   ...
}

Calling "changes" before "initialization" doesn't make any sense.

Par for the course with Angular... why would anyone expect init to happen before changes?

Was this page helpful?
0 / 5 - 0 ratings