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

时间:2014-07-15 09:02:28

标签: angularjs angularjs-directive angularjs-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>

我希望在更新bar的值时致电foo

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

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

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

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

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

5 个答案:

答案 0 :(得分:58)

如果您需要ngModel,则只需致电$setViewValue上的ngModelController,即隐式评估ng-change。链接函数的第四个参数应该是ngModelCtrl。以下代码将使ng-change适用于您的指令。

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

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

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

答案 1 :(得分: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
      };
    }
  };
});

更确切地说

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

我指定ng-chage会自动触发,如果 $viewValue的引用发生了变化。当您的ngModelstringint时,您不必担心。如果您的ngModel是一个对象而您刚刚更改了某些属性,则$setViewValue将不会评估ngChange

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

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

答案 2 :(得分:9)

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

在执行回调后,它会自动启动$digest周期。

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

$timeout(scope.ngChange, 0);

这样,回调的签名是什么并不重要,它将像你在父范围中定义一样执行。

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

答案 3 :(得分:0)

Samuli Ulmanen和lucienBertin的回答指出了它,尽管AngularJS文档中的一些进一步阅读提供了关于如何处理这一问题的进一步建议(见https://docs.angularjs.org/api/ng/type/ngModel.NgModelController)。

特别是在您将对象传递给$ setViewValue(myObj)的情况下。 AngularJS Documentatation声明:

  

当与标准输入一起使用时,视图值将始终是一个字符串(在某些情况下,它会被解析为另一个类型,例如输入[date]的Date对象。)但是,自定义控件也可能将对象传递给此方法。在这种情况下,我们应该在将对象传递给$ setViewValue之前复制该对象。这是因为ngModel不会对对象进行深度监视,它只会查找身份的更改。如果只更改对象的属性,则ngModel将不会意识到对象已更改,并且不会调用$ parsers和$ validators管道。因此,一旦将副本传递给$ setViewValue,就不应该更改副本的属性。否则,您可能会导致范围上的模型值更改不正确。

对于我的特定情况,我的模型是一个时刻日期对象,所以我必须首先克隆该对象,然后再调用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);
        }
    };
}

答案 4 :(得分:-2)

这里的根本问题是,在scope.updateModel完成执行之后发生的摘要周期之前,底层模型不会更新。如果ngChange函数需要更新的详细信息,那么这些详细信息可以明确提供给ngChange,而不是依赖先前已应用的模型更新。

这可以通过在调用ngChange时为值提供局部变量名称的映射来完成。在此方案中,您可以将模型的新值映射到可在ng-change表达式中引用的名称。

例如:

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