解释 ngModel 管道、解析器、格式化程序、viewChangeListeners 和 $watchers 的顺序

2023-11-26

提出这个问题并不容易,所以我将尝试用一个例子来解释我想知道的内容:

考虑这个简单的 angularjsapp: PLUNKER

angular.module('testApp', [])
.controller('mainCtrl', function($scope) {
  $scope.isChecked = false;
})
.directive("testDirective", function () {
    return {
        restrict: 'E',
        scope: {
            isChecked: '='
        },
        template: '<label><input type="checkbox" ng-model="isChecked" /> Is it Checked?</label>'+
                  '<p>In the <b>directive\'s</b> scope <b>{{isChecked?"it\'s checked":"it isn\'t checked"}}</b>.</p>'
    };
}); 

有了这个html:

  <body ng-controller="mainCtrl">
    <test-directive is-checked="isChecked"></test-directive>
    <p>In the <b>controller's</b> scope <b>{{isChecked?"it\'s checked":"it isn\'t checked"}}</b>.</p>
  </body>

The app:

  • 有一个名为“mainCtrl”的控制器,我们在其中定义了一个名为“isChecked”的范围变量
  • 它还具有一个名为“testDirective”的指令,该指令具有独立的范围和一个名为“isChecked”的绑定属性。
  • 在 html 中,我们在“mainCtrl”内部实例化“testDirective”,并将“mainCtrl”范围的“isChecked”属性与该指令的隔离范围的“isChecked”属性绑定。
  • 该指令呈现一个具有“isChecked”范围属性作为模型的复选框。
  • 当我们选中或取消选中该复选框时,我们可以看到两个范围的两个属性同时更新。

到目前为止,一切都很好。

现在让我们做一些改变,像这样:PLUNKER

angular.module('testApp', [])
.controller('mainCtrl', function($scope) {
  $scope.isChecked = false;
  $scope.doingSomething = function(){alert("In the controller's scope is " + ($scope.isChecked?"checked!":"not checked"))};
})
.directive("testDirective", function () {
    return {
        restrict: 'E',
        scope: {
            isChecked: '=',
            doSomething: '&'
        },
        template: '<label><input type="checkbox" ng-change="doSomething()" ng-model="isChecked" /> Is it Checked?</label>'+
                  '<p>In the <b>directive\'s</b> scope <b>{{isChecked?"it\'s checked":"it isn\'t checked"}}</b>.</p>'
    };
}); 

和这个:

<!DOCTYPE html>
<html ng-app="testApp">
  <head>
    <script data-require="[email protected]" data-semver="1.3.0-beta.5" src="https://code.angularjs.org/1.3.0-beta.5/angular.js"></script>
    <link rel="stylesheet" href="style.css" />
    <script src="script.js"></script>
  </head>
  <body ng-controller="mainCtrl">
    <test-directive is-checked="isChecked" do-something="doingSomething()"></test-directive>
    <p>In the <b>controller's</b> scope <b>{{isChecked?"it\'s checked":"it isn\'t checked"}}</b>.</p>
  </body>
</html>

我们唯一做的就是:

  • 在控制器的范围内定义一个函数,该函数执行以下操作window.alert指示控制器范围的“isChecked”属性是否已选中或未选中。 (我正在做一个window.alert故意的,因为我希望停止执行)
  • 将该函数绑定到指令中
  • 在指令的复选框的“ng-change”中触发该功能。

现在,当我们选中或取消选中该复选框时,我们会收到一条警报,在该警报中我们可以看到该指令的范围尚未更新。好的,所以人们会认为ng-change在模型更新之前触发,并且在显示警报时我们可以看到根据浏览器中呈现的文本“isChecked”在两个范围中具有相同的值。好吧,没什么大不了的,如果这就是“ng-change”的行为方式,那就这样吧,我们总是可以设置一个$watch并在那里运行该函数...但是让我们做另一个实验:

像这样:PLUNKER

.directive("testDirective", function () {
    return {
        restrict: 'E',
        scope: {
            isChecked: '=',
            doSomething: '&'
        },
        controller: function($scope){
          $scope.internalDoSomething = function(){alert("In the directive's scope is " + ($scope.isChecked?"checked!":"not checked"))};
        },
        template: '<label><input type="checkbox" ng-change="internalDoSomething()" ng-model="isChecked" /> Is it Checked?</label>'+
                  '<p>In the <b>directive\'s</b> scope <b>{{isChecked?"it\'s checked":"it isn\'t checked"}}</b>.</p>'
    };
}); 

现在我们只是使用指令作用域的函数来完成与控制器作用域的函数所做的相同的事情,但是这次事实证明模型已经更新了,所以看起来此时指令的范围已更新,但控制器的范围未更新......奇怪!

让我们确保情况确实如此:PLUNKER

angular.module('testApp', [])
.controller('mainCtrl', function($scope) {
  $scope.isChecked = false;
  $scope.doingSomething = function(directiveIsChecked){
    alert("In the controller's scope is " + ($scope.isChecked?"checked!":"not checked") + "\n"
        + "In the directive's scope is " + (directiveIsChecked?"checked!":"not checked") );
  };
})
.directive("testDirective", function () {
    return {
        restrict: 'E',
        scope: {
            isChecked: '=',
            doSomething: '&'
        },
        controller: function($scope){
          $scope.internalDoSomething = function(){ $scope.doSomething({directiveIsChecked:$scope.isChecked}) }; 
        },
        template: '<label><input type="checkbox" ng-change="internalDoSomething()" ng-model="isChecked" /> Is it Checked?</label>'+
                  '<p>In the <b>directive\'s</b> scope <b>{{isChecked?"it\'s checked":"it isn\'t checked"}}</b>.</p>'
    };
}); 

这次我们使用指令作用域的函数来触发控制器的绑定函数,并且我们将带有指令作用域值的参数传递给控制器​​的函数。现在在控制器的函数中,我们可以确认我们在上一步中已经怀疑的内容,即:隔离范围首先更新,然后是ng-change被触发,并且在此之后指令范围的绑定不会被更新。

现在,最后我的问题是:

  • 在做其他事情之前,AngularJS 不应该同时更新所有绑定的属性吗?
  • 谁能给我详细解释一下内部发生的事情,以证明这种行为的合理性?

换句话说:如果“ng-change”在模型更新之前被触发,我可以理解这一点,但我很难理解在更新模型之后和完成填充更改之前触发函数的绑定属性。

如果您读到这里:恭喜您并感谢您的耐心等待!

Josep


总结一下问题,ngModelController之前要经过一个过程watches将被解雇。您正在记录外部$scope之前的财产ngModelController已处理更改并导致 $digest 循环,这将依次触发$watchers。我不会考虑model更新到那时。

这是一个复杂的系统。我做了这个demo作为参考。我建议更改return值、键入和单击 - 只是以各种方式摆弄它并检查日志。这使得一切如何运作很快变得清晰起来。

演示(玩得开心!)

ngModelController有它自己的函数数组来运行作为对不同变化的响应。

ngModelController有两种“管道”来确定如何处理某种变更。这些允许开发人员控制值的流动。

如果范围属性指定为ngModel变化,则$formatter管道将运行。该管道用于确定值如何来自$scope应显示在视图中,但保留模型。所以,ng-model="foo" and $scope.foo = '123',通常会显示123在输入中,但格式化程序可能返回1-2-3或任何值。$scope.foo仍然是 123,但它显示为格式化程序返回的内容。

$parsers处理同样的事情,但相反。当用户输入内容时,$parser 管道就会运行。无论什么$parser返回值将被设置为ngModel.$modelValue。所以,如果用户输入abc$parser回报a-b-c,那么视图不会改变,但是$scope.foo now is a-b-c.

在任一之后$formatter or $parser runs, $validators将被运行。用于验证器的任何属性名称的有效性将由验证函数的返回值设置(true or false).

$viewChangeListeners在视图更改后触发,而不是模型更改后触发。这一点特别令人困惑,因为我们指的是$scope.foo并不是ngModel.$modelValue。视图不可避免地会更新ngModel.$modelValue(除非在管道中被阻止),但这不是model change我们指的是。基本上,$viewChangeListeners被解雇后$parsers并且不是之后$formatters。因此,当视图值发生变化(用户类型)时,$parsers, $validators, then $viewChangeListeners。欢乐时光=D

所有这一切都发生在内部ngModelController。在此过程中,ngModel对象没有像您预期的那样更新。管道正在传递将影响该对象的值。在该过程结束时,ngModel对象将被更新为正确的$viewValue and $modelValue.

最后,ngModelController已完成,并且$digest将发生循环以允许应用程序的其余部分响应所产生的更改。

这是演示中的代码,以防万一发生任何情况:

<form name="form">
  <input type="text" name="foo" ng-model="foo" my-directive>
</form>
<button ng-click="changeModel()">Change Model</button>
<p>$scope.foo = {{foo}}</p>
<p>Valid: {{!form.foo.$error.test}}</p>

JS:

angular.module('myApp', [])

.controller('myCtrl', function($scope) {

  $scope.foo = '123';
  console.log('------ MODEL CHANGED ($scope.foo = "123") ------');

  $scope.changeModel = function() {
    $scope.foo = 'abc';
    console.log('------ MODEL CHANGED ($scope.foo = "abc") ------');
  };

})

.directive('myDirective', function() {
  var directive = {
    require: 'ngModel',
    link: function($scope, $elememt, $attrs, $ngModel) {

      $ngModel.$formatters.unshift(function(modelVal) {
        console.log('-- Formatter --', JSON.stringify({
          modelVal:modelVal,
          ngModel: {
            viewVal: $ngModel.$viewValue,
            modelVal: $ngModel.$modelValue
          }
        }, null, 2))
        return modelVal;
      });

      $ngModel.$validators.test = function(modelVal, viewVal) {
        console.log('-- Validator --', JSON.stringify({
          modelVal:modelVal,
          viewVal:viewVal,
          ngModel: {
            viewVal: $ngModel.$viewValue,
            modelVal: $ngModel.$modelValue
          }
        }, null, 2))
        return true;
      };

      $ngModel.$parsers.unshift(function(inputVal) {
        console.log('------ VIEW VALUE CHANGED (user typed in input)------');
        console.log('-- Parser --', JSON.stringify({
          inputVal:inputVal,
          ngModel: {
            viewVal: $ngModel.$viewValue,
            modelVal: $ngModel.$modelValue
          }
        }, null, 2))
        return inputVal;
      });

      $ngModel.$viewChangeListeners.push(function() {
        console.log('-- viewChangeListener --', JSON.stringify({
          ngModel: {
            viewVal: $ngModel.$viewValue,
            modelVal: $ngModel.$modelValue
          }
        }, null, 2))
      });

      // same as $watch('foo')
      $scope.$watch(function() {
        return $ngModel.$viewValue;
      }, function(newVal) {
        console.log('-- $watch "foo" --', JSON.stringify({
          newVal:newVal,
          ngModel: {
            viewVal: $ngModel.$viewValue,
            modelVal: $ngModel.$modelValue
          }
        }, null, 2))
      });


    }
  };

  return directive;
})

;
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

解释 ngModel 管道、解析器、格式化程序、viewChangeListeners 和 $watchers 的顺序 的相关文章

随机推荐