如何实现自定义指令的ng-change

我有一个像模板的指令

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

我的指令被声明为:

 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(); } } } 

我想在点击一个项目时调用ng-change ,并且foo的值已经被更改。

也就是说,如果我的指令被实现为:

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

我希望在foo的值更新时调用bar

使用上面给出的代码, ngChange被成功调用,但是用旧的foo值而不是新的更新值调用。

解决这个问题的一个办法是在超时时间内调用ngChange以便在将来的某个时刻执行它,当foo的值已经被改变时。 但是这个解决scheme使得我可以松散地控制事物执行的顺序,我认为应该有一个更优雅的解决scheme。

我也可以在父范围内使用foo而不是一个ngChange ,但是这个解决scheme并没有给ngChange方法ngChange ,而且我被告知监视器是很好的内存消费者。

有没有办法使ngChange同步执行没有超时或观察员?

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

如果你需要ngModel你可以在$setViewValue上调用$setViewValue ngModelController ,隐式地评估ng-change 。 链接函数的第四个参数应该是ngModelCtrl。 下面的代码将会使你的指令的ng-change工作。

 link : function(scope, element, attrs, ngModelCtrl){ scope.updateModel = function(item) { ngModelCtrl.$setViewValue(item); } } 

为了解决您的解决scheme,请从myDirective的隔离范围中删除ngChange和ngModel。

这是一个庞然大物: http ://plnkr.co/edit/UefUzOo88MwOMkpgeX07? p= preview

TL;博士

根据我的经验,你只需要从ngModelCtrlinheritance。 当你使用ngModelCtrl.$setViewValue方法时, ng-changeexpression式将被自动计算

 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 }; } }; }); 

更确切地说

ng-changengModelCtrl.$commitViewValue()期间被评估ngModelCtrl.$commitViewValue() 如果 ngModelCtrl.$commitViewValue()的对象引用已经改变。 如果你不使用触发器参数或者没有精确的ngModelOptions $setViewValue(value, trigger) $commitViewValue()方法会自动被$setViewValue(value, trigger) 调用 。

我指定ng-chage会自动触发, 如果 $viewValue的引用改变。 当你的ngModel是一个string或一个int ,你不必担心它。 如果你的ngModel是一个对象,而你只是改变它的一些属性,那么$setViewValue将不会评估ngChange

如果我们从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 }; 

经过一番研究,似乎最好的方法是使用$timeout(callback, 0)

它会在执行callback后自动启动$digest循环。

所以,就我而言,解决scheme就是使用

 $timeout(scope.ngChange, 0); 

这样,你的callback的签名是什么都没有关系,它将会像你在父范围中定义一样被执行。

以下是有这种变化的plunkr: http ://plnkr.co/edit/9MGptJpSQslk8g8tD2bZ?p=preview

Samuli Ulmanen和lucienBertin的答案指出,虽然在AngularJS文档中进一步的阅读提供了进一步的build议如何处理(请参阅https://docs.angularjs.org/api/ng/type/ngModel.NgModelController )。

特别是在你将对象传递给$ setViewValue(myObj)的情况下。 AngularJS Documentatation指出:

与标准input一起使用时,视图值将始终是一个string(在某些情况下,该string被parsing为另一种types,例如input[date]的Date对象)。但是,自定义控件也可能将对象传递给此方法。 在这种情况下,我们应该先将对象的副本传递给$ setViewValue。 这是因为ngModel没有深入观察对象,只是寻找身份的改变。 如果你只改变对象的属性,那么ngModel不会意识到对象已经改变,不会调用$ parsers和$ validatorspipe道。 出于这个原因,一旦它传递给$ setViewValue,你不应该改变它的属性。 否则,您可能会导致范围上的模型值更改不正确。

对于我的具体情况,我的模型是一个时间date对象,所以我必须先克隆对象,然后调用setViewValue。 我很幸运,因为时刻提供了一个简单的克隆方法: var b = moment(a);

 link : function(scope, elements, attrs, ctrl) { scope.updateModel = function (value) { if (ctrl.$viewValue == value) { var copyOfObject = moment(value); ctrl.$setViewValue(copyOfObject); } else { ctrl.$setViewValue(value); } }; } 

这里的基本问题是,直到在scope.updateModel执行完成之后的摘要循环才会更新基础模型。 如果ngChange函数需要正在进行的更新的细节,那么这些细节可以显式提供给ngChange ,而不是依赖于以前应用的模型更新。

在调用ngChange时,可以通过提供局部variables名称的值来ngChange 。 在这种情况下,您可以将模型的新值映射到可在ng-changeexpression式中引用的名称。

例如:

 scope.updateModel = function(item) { scope.ngModel = item; scope.ngChange({newValue: item}); } 

在HTML中:

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

请参阅: http : //plnkr.co/edit/4CQBEV1S2wFFwKWbWec3?p=preview