Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement an ng-change for a custom directive

I have a directive with a template like

<div>     <div ng-repeat="item in items" ng-click="updateModel(item)"> <div> 

My directive is declared as:

return {     templateUrl: '...',     restrict: 'E',     require: '^ngModel',     scope: {         items: '=',         ngModel: '=',         ngChange: '&'     },     link: function postLink(scope, element, attrs)      {         scope.updateModel = function(item)         {              scope.ngModel = item;              scope.ngChange();         }     } } 

I would like to have ng-change called when an item is clicked and the value of foo has been changed already.

That is, if my directive is implemented as:

<my-directive items=items ng-model="foo" ng-change="bar(foo)"></my-directive> 

I would expect to call bar when the value of foo has been updated.

With code given above, ngChange is successfully called, but it is called with the old value of foo instead of the new updated value.

One way to solve the problem is to call ngChange inside a timeout to execute it at some point in the future, when the value of foo has been already changed. But this solution make me loose control over the order in which things are supposed to be executed and I assume that there should be a more elegant solution.

I could also use a watcher over foo in the parent scope, but this solution doesn't really give an ngChange method to be implmented and I have been told that watchers are great memory consumers.

Is there a way to make ngChange be executed synchronously without a timeout or a watcher?

Example: http://plnkr.co/edit/8H6QDO8OYiOyOx8efhyJ?p=preview

like image 392
htellez Avatar asked Jul 15 '14 09:07

htellez


2 Answers

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

like image 50
Samuli Ulmanen Avatar answered Oct 06 '22 14:10

Samuli Ulmanen


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 }; 
like image 42
lucienBertin Avatar answered Oct 06 '22 14:10

lucienBertin