53

我有一个带有模板的指令

<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

4

5 回答 5

60

如果您需要ngModel,您可以调用$setViewValuengModelController它会隐式评估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

于 2014-09-22T12:09:33.203 回答
15

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 的对象引用已更改。如果您不使用 trigger 参数或没有精确任何ngModelOptions$commitViewValue() ,则会自动调用该方法。$setViewValue(value, trigger)

我指定如果更改的引用ng-change将自动触发。当 your是 a或 an时,您不必担心。如果你是一个对象并且你只是改变它的一些属性,那么不会 eval 。$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
};
于 2015-11-30T14:41:52.030 回答
10

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

它会在回调执行后自动启动一个$digest循环。

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

$timeout(scope.ngChange, 0);

这样,回调的签名是什么并不重要,它会按照您在父范围中定义的方式执行。

这是具有此类更改的 plunkr: http ://plnkr.co/edit/9MGptJpSQslk8g8tD2bZ?p=preview

于 2014-08-03T04:28:00.803 回答
0

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

特别是在您将对象传递给 $setViewValue(myObj) 的情况下。AngularJS 文档状态:

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

对于我的具体情况,我的模型是一个时刻日期对象,所以我必须先克隆该对象,然后再调用 setViewValue。我很幸运,因为 moment 提供了一个简单的克隆方法: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);
        }
    };
}
于 2016-07-04T13:57:40.470 回答
-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

于 2014-07-15T13:19:25.733 回答