如何实现自定义指令的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-change
expression式将被自动计算
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-change
在ngModelCtrl.$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-change
expression式中引用的名称。
例如:
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