【问题标题】:ngChange fires before value makes it out of isolate scopengChange 在 value 超出隔离范围之前触发
【发布时间】:2015-10-16 19:27:01
【问题描述】:
//main controller
angular.module('myApp')
.controller('mainCtrl', function ($scope){
    $scope.loadResults = function (){
        console.log($scope.searchFilter);
    };
});

// directive
angular.module('myApp')
.directive('customSearch', function () {
    return {
        scope: {
            searchModel: '=ngModel',
            searchChange: '&ngChange',
        },
        require: 'ngModel',
        template: '<input type="text" ng-model="searchModel" ng-change="searchChange()"/>',
        restrict: 'E'
    };
});

// html
<custom-search ng-model="searchFilter" ng-change="loadResults()"></custom-search>

这里有一个简化的指令来说明。当我输入输入时,我希望loadResults 中的console.log 准确地注销我已经输入的内容。它实际上记录了一个字符,因为loadResults 在主控制器中的searchFilter var 正在从指令接收新值之前运行。然而,在指令中登录,一切都按预期工作。为什么会这样?

我的解决方案

在我的简单示例中了解了 ngChange 发生的情况后,我意识到我的实际问题更加复杂,因为我实际传入的 ngModel 是一个对象,我正在更改其属性,并且另外,我正在使用带有此指令的表单验证作为输入之一。我发现在指令中使用 $timeout 和 $eval 解决了我所有的问题:

//main controller
angular.module('myApp')
.controller('mainCtrl', function ($scope){
    $scope.loadResults = function (){
        console.log($scope.searchFilter);
    };
});

// directive
angular.module('myApp')
.directive('customSearch', function ($timeout) {
    return {
        scope: {
            searchModel: '=ngModel'
        },
        require: 'ngModel',
        template: '<input type="text" ng-model="searchModel.subProp" ng-change="valueChange()"/>',
        restrict: 'E',
        link: function ($scope, $element, $attrs, ngModel)
        {
            $scope.valueChange = function()
            {
                $timeout(function()
                {
                    if ($attrs.ngChange) $scope.$parent.$eval($attrs.ngChange);
                }, 0);
            };
        }
    };
});

// html
<custom-search ng-model="searchFilter" ng-change="loadResults()"></custom-search>

【问题讨论】:

    标签: angularjs angularjs-ng-change isolate-scope


    【解决方案1】:

    行为的原因,正如另一个答案中正确指出的那样,是因为双向绑定没有机会在 searchChange() 之前更改外部 searchFilter,因此,loadResults()被调用了。

    但是,由于两个原因,该解决方案非常老套。

    调用者(指令的用户)不需要知道$timeout 的这些变通方法。如果不出意外,$timeout 应该在指令中而不是在视图控制器中完成。

    还有两个 - OP 也犯了一个错误 - 使用 ng-model 伴随着此类指令的用户的其他“期望”。拥有ng-model 意味着其他指令,如验证器、解析器、格式化程序和视图更改侦听器(如ng-change)可以与它一起使用。要正确支持它,需要require: "ngModel",而不是通过scope: {} 绑定到它的表达式。否则,事情将无法按预期进行。

    这是它的完成方式 - 另一个示例,请参阅 the official documentation 以创建自定义输入控件。

    scope: true, // could also be {}, but I would avoid scope: false here
    template: '<input ng-model="innerModel" ng-change="onChange()">',
    require: "ngModel",
    link: function(scope, element, attrs, ctrls){
      var ngModel = ctrls; // ngModelController
    
      // from model -> view
      ngModel.$render = function(){
        scope.innerModel = ngModel.$viewValue;
      }
    
      // from view -> model
      scope.onChange = function(){
        ngModel.$setViewValue(scope.innerModel);
      }
    }
    

    然后,ng-change 会自动工作,支持ngModel 的其他指令也会自动工作,例如ng-required

    【讨论】:

    • 我意识到我在示例中遗漏了一些关键信息。我的外部 ngModel 实际上是一个对象,我试图在指令中使用多个输入来更改该对象的属性,但仍将指令视为单个输入。
    • @RobbyAllsopp,好的,我给你的方法没问题。但是您使用$timeout 并直接绑定到ngModel 的方法是错误的,并且可能会产生意想不到的结果。例如,假设您想在指令中使用 ng-model-optionsdebounce - 它不会产生任何效果。就像我说的,使用ng-model意味着你支持ngModelController“框架”。
    • 好点它会用 ngModel 搞砸一些其他的事情。我看不到$timeout 的解决方法,但如果我仍然希望验证工作,但我可能会将$eval 换成ngModel.$setViewValue(angular.copy($scope.searchModel))。我可能会切换到那个,尽管我对每次发生任何变化时都必须复制整个对象并不感到兴奋
    • @RobbyAllsopp,我不确定您所说的验证是什么意思。通过ngModel 支持验证。事实上,我相当肯定,使用您的 $timeout 方法它不会起作用,因为直接通过隔离范围绑定进行的更改会绕过解析器和验证器。而且,您需要为“更改”创建一个新对象以进行注册。
    • Angular 有什么变化吗?这对我不起作用。对 ngModel 的更改会更改您的文本框,但对文本框的更改不会更新 ngModel。不过,肯定会调用 onChange 。此外,一旦文本框值与 ngModel 不同(通过手动输入),当 ngModel 在其他地方更改时,它甚至不再更新。最后,不是require: '^ngModel'吗?你会做 Plunker 吗?
    【解决方案2】:

    您在标题中回答了自己的问题! '=' 被观看,'&amp;' 未被观看

    • 角度以外的某个地方:

      输入视图值变化

    • 下一个摘要周期:

      ng-model 值更改并触发ng-change()

      ng-change 添加了一个 $viewChangeListener 并被称为同一个循环。 看: ngModel.js#L714ngChange.js 实现。

      当时$scope.searchFilter 还没有更新。 Console.log 的旧值

    • 下一个摘要循环: searchFilter 由数据绑定更新。

    更新:仅作为 POC,您需要 1 个额外的周期才能传播值,您可以执行以下操作。请参阅其他 anwser(@NewDev 以获得更简洁的方法)。

    .controller('mainCtrl', function ($scope, $timeout){
        $scope.loadResults = function (){
            $timeout(function(){
               console.log($scope.searchFilter);
            });
        };
    });
    

    【讨论】:

    • 好答案,我在最后一个小时调试 Angular 以查看发生了什么。你能解释一下为什么指令需要一个额外的摘要周期来更新父范围吗?是不是因为摘要从$rootScope 开始向下传播?如果一路上有第三个ng-change,该值是否需要更多的消化周期才能到达控制器?
    • 作用域是隔离的,指令不会直接修改父作用域。他们保持= vars 同步的方法是使用著名的角度“数据绑定”.. 这基本上是$watch。每个摘要循环角度都会进行脏检查,当它检测到更改时,会“手动”复制该值。因此,当指令变量更改时,需要将 $digest 复制到“父级”(而 & 是直接访问)
    • 对于 nitpick,input 触发摘要循环(它不会在摘要期间“发生”),并在其第一次迭代中设置 searchModel,并且searchChange()(导致loadResults())被触发......然后,在第二次迭代中,searchFilter被设置......但是这个解决方法非常hacky。
    • 您说的第一个周期不在一个周期中是对的! (正在考虑 ngModel $viewValue.. 但 ngModel 并不是那么简单)我会编辑。关于 hacky,这正是问题所在……“发生了什么事?”
    • @AlejandroCotroneo,是的,我明白了......措辞可能会更好地说这是 POC,以免暗示这实际上是一个有效的解决方法。跨度>
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-07-26
    • 1970-01-01
    • 2021-05-20
    • 2014-10-12
    • 1970-01-01
    • 2012-02-09
    • 2013-10-27
    相关资源
    最近更新 更多