Angular 1.5 component onChanges is not working
Since we have pretty big Angular 1.x application, we can’t upgrade it fully to Angular 2 but I love the new architecture. Version 1.5 brings amazing component
s to the old same app. As all cool stuff, it lacks documentation 😉
So, here is a question. I have those two lines in the controller’s definition:
this.$onInit = setType;
this.$onChanges = setType;
the first is working, whilst the second isn’t. I am using '<'
binding. So on the first load, the component’s state is set according to passed values, whilst the changes are not being reflected. I got the hope that it should work from [1] and [2].
[1] https://docs.angularjs.org/guide/component
[2] https://angular.io/docs/js/latest/api/core/OnChanges-interface.html
UPD Ok, I have learnt that it is not supposed to work:
https://github.com/angular/angular.js/issues/14030
Does anybody know good workarounds?
UPD2 It works as of 1.5.3
As of AngularJs 1.5.3, supposing ctrl.someModel is bound one-way in a child component, the following won’t trigger $onChanges.
function get() {
api.getData().then( (data) => {
ctrl.someModel.data = data
}
}
It seems like updating properties of an object won’t be recognized as an update.
This is how I currently get around it. I don’t believe it to be the best solution, but it triggers $onChanges. I create a deep copy of my initial model, add the data as one of it’s properties, and then set my initial model to the value of the new object. Essentially, I update the whole object, which is picked up by the lifecycle hook:
function get() {
api.getData().then( (data="42") => {
const updatedModel = angular.copy(ctrl.someModel)
updatedModel.data = data
ctrl.someModel = updatedModel
}
}
And in the child component (assuming the model as been binded as ‘data’):
this.$onInit = function(bindings) {
if (bindings.data && bindings.data.currentValue) {
console.log(bindings.data.currentValue) // '42'
}
}
Dealing with $onChanges
is tricky. Actually, thats why in version 1.5.8 they introduced the $doCheck
, similar to Angular 2 ngDoCheck.
This way, you can manually listen to changes inside the object being listened, which does not occur with the $onChanges
hook (called only when the reference of the object is changed). Its the same thing, but it gets called for every digest cycle allowing you to check for changes manually (but better then watches).
As far as I can tell, both the $onChanges
and $onInit
method should work with AngularJS version 1.5.3.
I’ve created a plnkr that demonstrates both usages.
It has two components, an outer and an inner component, where a value is bound from the outer to the inner component using the one-way binding operator <
. An input field updates the outer component’s value. On every keyboard input into the input field, the $onChanges
method is fired (open your console to see).
angular.module("app", [])
.component("outer", {
restrict: "E",
template: `<input ng-model=$ctrl.value> <br />
<inner value="$ctrl.value">
</inner>`
})
.component("inner", {
restrict: "E",
template: `{{$ctrl.value}}`,
bindings: {
value: "<"
},
controller: function(){
function setType() {
console.log("called");
}
this.$onInit = setType;
this.$onChanges = setType;
}
});
Basically, $onChanges angular lifecycle hook trigger when angular find the change in reference ( not the property in the changed object ), so in order to invoke the $onChanges in the child, in the parent, assign the new object. for example,
angular.module("app", [])
.component("outer", {
restrict: "E",
controller : function(){
this.value = {};
this.userButtonClick = function(someValue){
this.value = angular.copy(someValue);
}
},
template: `<input ng-click="$ctrl.userButtonClick({somevalue : "value"})" />
<br />
<inner value="$ctrl.value">
</inner>`
})
.component("inner", {
restrict: "E",
template: `{{$ctrl.value}}`,
bindings: {
value: "<"
},
controller: function(){
function setType() {
console.log("called");
}
this.$onInit = setType;
this.$onChanges = function(changeObj){
console.log("changed value",changeObj.value.currentValue);
}
}
});
Don’t use $doCheck unless you really want to trigger a callback in every digest cycle, because it is invoked in every $digest cycle no matter some binding change or not.