I don't understand why $onChanges isn't kicked off when I change a bound primitive in an input. Can someone see what I've done wrong, and explain this in an uncomplicated way? I made a plunkr of a quick test application after I couldn't get it to work in my actual application either.
angular
.module('test', [])
.component('test', {
template: '<child application="vm.application"></child>',
controller: 'testCtrl as vm'
})
.controller('testCtrl', function() {
var vm = this;
vm.$onInit = function () {
vm.application = {
data: {
name: 'Test'
}
}
};
})
.component('child', {
template: '<input type="text" ng-model="vm.application.data.name">',
bindings: {
application: '<'
},
controller: 'childCtrl as vm'
})
.controller('childCtrl', function() {
var vm = this;
vm.$onChanges = function (changes) {
console.log('CHANGED: ', changes);
};
})
The $onChanges
method is not called for changes on subproperties of an object. Default changes to objects generally follow this sequence within a components lifetime:
undefined
undefined
to {}
or { someAttribute: someValue, .. }
{..}
to undefined
if you delete
the object in a parent scope)In order to watch subproperties you could use the $doCheck
method that was added in 1.5.8. It is called on every digest cycle and it takes no parameters. With great power comes great responsibility. In that method you would put logic that detects whether a certain value has been updated or not - the new value will already be updated in the controller's scope, you just need to find a way to determine if the value changed compared to the previously known value.
You could set a previousValueOfObjectAttribute
variable on the controller before you start to expect changes to this specific attribute (e.g. when subcomponent B calls an output binding
function in component A, based on which the target object - which is an input binding to B - in A changes). In cases where it is not predictable when the change is about to occur, you could make a copy of the specific atributes of interest after any change observed via the $doCheck
method.
In my specific use case, I did not explicitly check between an old and new value, but I used a promise (store $q.defer().promise
) with the intention that any change I would 'successfully' observe in the $doCheck
method would resolve that promise. My controller then looked something like the following:
dn.$doCheck = function () {
if (dn.waitForInputParam &&
dn.waitForInputParam.promise.$$state.status === 0 &&
dn.targetObject.targetAttribute !== false)
dn.waitForInputParam.resolve(dn.targetObject.targetAttribute);
}
dn.listenToInputChange = function () {
dn.waitForInputParam = $q.defer();
dn.waitForInputParam.promise.then(dn.onInputParamChanged);
}
dn.onInputParamChanged = function (value) {
// do stuff
//
// start listening again for input changes -- should be async to prevent infinite $digest loop
setTimeout(dn.listenToInputChange, 1);
}
(w.r.t. promise.$$state.status
, see this post).
For all other intents and purposes, watching changes to primitive data types, you should still use $onChanges
. Reference: https://docs.angularjs.org/guide/component