How to implement an ng-change for a custom directive
tl;dr
In my experience you just need to inherit from the ngModelCtrl. the ng-change
expression will be automatically evaluated when you use the method ngModelCtrl.$setViewValue
angular.module("myApp").directive("myDirective", function(){
return {
require:"^ngModel", // this is important,
scope:{
... // put the variables you need here but DO NOT have a variable named ngModel or ngChange
},
link: function(scope, elt, attrs, ctrl){ // ctrl here is the ngModelCtrl
scope.setValue = function(value){
ctrl.$setViewValue(value); // this line will automatically eval your ng-change
};
}
};
});
More precisely
ng-change
is evaluated during the ngModelCtrl.$commitViewValue()
IF the object reference of your ngModel has changed. the method $commitViewValue()
is called automatically by $setViewValue(value, trigger)
if you do not use the trigger argument or have not precised any ngModelOptions.
I specified that the ng-change
would be automatically triggered if the reference of the $viewValue
changed. When your ngModel
is a string
or an int
, you don't have to worry about it. If your ngModel
is an object and your just changing some of its properties, then $setViewValue
will not eval ngChange
.
If we take the code example from the start of the post
scope.setValue = function(value){
ctrl.$setViewValue(value); // this line will automatically evalyour ng-change
};
scope.updateValue = function(prop1Value){
var vv = ctrl.$viewValue;
vv.prop1 = prop1Value;
ctrl.$setViewValue(vv); // this line won't eval the ng-change expression
};
If you require ngModel
you can just call $setViewValue
on the ngModelController
, which implicitly evaluates ng-change
. The fourth parameter to the linking function should be the ngModelCtrl. The following code will make ng-change
work for your directive.
link : function(scope, element, attrs, ngModelCtrl){
scope.updateModel = function(item) {
ngModelCtrl.$setViewValue(item);
}
}
In order for your solution to work, please remove ngChange and ngModel from isolate scope of myDirective.
Here's a plunk: http://plnkr.co/edit/UefUzOo88MwOMkpgeX07?p=preview
After some research, it seems that the best approach is to use $timeout(callback, 0)
.
It automatically launches a $digest
cycle just after the callback is executed.
So, in my case, the solution was to use
$timeout(scope.ngChange, 0);
This way, it doesn't matter what is the signature of your callback, it will be executed just as you defined it in the parent scope.
Here is the plunkr with such changes: http://plnkr.co/edit/9MGptJpSQslk8g8tD2bZ?p=preview