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

hte*_*lez 51 angularjs angularjs-directive angularjs-ng-change

我有一个模板的指令

<div>
    <div ng-repeat="item in items" ng-click="updateModel(item)">
<div>
Run Code Online (Sandbox Code Playgroud)

我的指令被声明为:

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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我希望ng-change在单击某个项目时调用,foo并且已经更改了值.

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

<my-directive items=items ng-model="foo" ng-change="bar(foo)"></my-directive>
Run Code Online (Sandbox Code Playgroud)

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

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

解决该问题的一种方法是调用ngChange内部超时以在将来的某个时刻执行它,此时值foo已经更改.但是这个解决方案让我可以放松地控制事情的执行顺序,我认为应该有一个更优雅的解决方案.

我也可以foo在父作用域中使用一个观察者,但是这个解决方案并没有真正给出一个ngChange方法,我被告知观察者是伟大的记忆消费者.

有没有办法在ngChange没有超时或观察者的情况下同步执行?

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

Sam*_*nen 58

如果你需要ngModel,你可以叫$setViewValuengModelController,它隐评估ng-change.链接函数的第四个参数应该是ngModelCtrl.以下代码将为ng-change您的指令工作.

link : function(scope, element, attrs, ngModelCtrl){
    scope.updateModel = function(item) {
        ngModelCtrl.$setViewValue(item);
    }
}
Run Code Online (Sandbox Code Playgroud)

为了使您的解决方案有效,请从myDirective的隔离范围中删除ngChange和ngModel.

这是一个插件:http://plnkr.co/edit/UefUzOo88MwOMkpgeX07?p = preview

  • 无论如何在没有从隔离范围中移除ngModel的情况下执行此操作?尝试使用ngChange实现双向绑定,它已经变得复杂/低效/sf/ask/2140318141/ (2认同)

小智 14

TL;博士

根据我的经验,您只需要从ngModelCtrl继承.的ng-change表达式将当您使用的方法进行自动评估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
      };
    }
  };
});
Run Code Online (Sandbox Code Playgroud)

更确切地说

ng-changengModelCtrl.$commitViewValue() IF期间评估ngModel的对象引用已更改.如果您不使用trigger参数或未准备任何ngModelOptions$commitViewValue(),$setViewValue(value, trigger)则会自动调用该方法.

我指定如果更改的引用ng-chage将自动触发.当你是a 或a时,你不必担心它.如果你是一个对象而你只是改变了它的一些属性,那么就不会进行评估.$viewValuengModelstringintngModel$setViewValuengChange

如果我们从帖子的开头拿到代码示例

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
};
Run Code Online (Sandbox Code Playgroud)

  • 伟大的洞察力。阅读您的描述,我想我可以手动调用 $commitViewValue() 来强制 ngChange(在更新现有对象的情况下),但事实并非如此。AngularJS 文档建议“自定义控件也可能将对象传递给此方法。在这种情况下,我们应该在将对象传递给 $setViewValue 之前制作该对象的副本。”。请参阅 https://docs.angularjs.org/api/ng/type/ngModel.NgModelController (2认同)

hte*_*lez 9

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

$digest在执行回调后立即自动启动一个循环.

所以,在我的情况下,解决方案是使用

$timeout(scope.ngChange, 0);
Run Code Online (Sandbox Code Playgroud)

这样,回调的签名无关紧要,它将按照您在父作用域中定义的那样执行.

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

  • 就像一个FYI一样,这个工作的原因是因为`$ timeout`会自动将函数包装在`$ scope.$ apply`中,这会导致$ digest循环被启动.如果您不想这样,则将第三个布尔参数传递给$ timeout,指示它不执行此操作. (3认同)