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);
};
})
One-way data binding in AngularJS means binding data from Model to View (Data flows from the scope/controller to the view). 'ng-bind' is an angular directive used for achieving one-way data binding.
AngularJS creates a two way data-binding between the select element and the $ctrl. orderProp model. $ctrl. orderProp is then used as the input for the orderBy filter.
Data binding in AngularJS is the synchronization between the model and the view. When data in the model changes, the view reflects the change, and when data in the view changes, the model is updated as well.
AngularJS Scope The scope is the binding part between the HTML (view) and the JavaScript (controller). The scope is an object with the available properties and methods. The scope is available for both the view and the controller.
The $onChanges is called when the components inits, it's called for the first change and than is not called for the rest of the changes. The property that changes is an object but the changes itself is done on the object reference not on the one of the properties. This works with angular 1.5.3.
Components have a well-defined public API - Inputs and Outputs:However, scope isolation only goes so far, because AngularJS uses two-way binding. So if you pass an object to a component like this - bindings: {item: '='}, and modify one of its properties, the change will be reflected in the parent component.
This is JS behavior, not Angular fault — but it’s easy to think that Angular will take care about it, while it won’t. What is real example of this situation? Imagine you have a select component which encapsulates common behavior like option translation, proper styling etc, and it takes array with options as binding:
C omponent bindings from Angular 1.5 had few difficulties. Although my problems may seem simple for experienced developers, I hope this post will help to protect somebody from unnecessary waste of time.
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
It's $onChanges
and not $onChange
.
Also, the onChange only updates when the parent value is changed, not the child. Take a look at this plunkr. Note the console.log only fires when you type in the first input.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With