在解释ngModel管道的顺序,解析器,格式化,viewChangeListeners和$观察家 [英] Explaining the order of the ngModel pipeline, parsers, formatters, viewChangeListeners, and $watchers
问题描述
这并不容易帧这个问题,所以我会尽量解释我想知道的一个例子是什么:
It's not easy to frame this question, so I will try to explain what I want to know with an example:
考虑一个简单的angularjs 应用
的 PLUNKER 的
Consider this simple angularjs app
: 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>'
};
});
通过这个网站:
<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>
该应用程序:
- 有一个所谓的控制器:mainCtrl在这里我们定义了一个名为器isChecked作用域变量
- 它也有称为testDirective与一个孤立的范围某个指令指向和所谓的器isChecked绑定属性
- 而在HTML我们实例化testDirective中的mainCtrl内和有约束力的器isChecked的mainCtrl作用域属性与器isChecked的指令的隔离范围的属性。
- 该指令使得它有一个复选框器isCheckedscope属性的一种模式。
- 当我们选中或取消选中,我们可以看到,无论作用域属性都同时更新的复选框。
到目前为止,一切都很好。
So far, so good.
现在,让我们稍作改动,这样的:的 PLUNKER 的
Now let's make a little change, like this: 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="angular.js@1.3.0-beta.5" 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>
这是我们所做的唯一事情是:
The only thing that we have done is:
- 在做控制器的范围定义函数的
window.alert
指示是否控制器的范围的器isChecked'属性被选中或取消选中。 (我做了window.alert
故意的,因为我想停止执行) - 绑定该功能进入指令
- 在该指令触发该功能的复选框的NG变化。
- Define a function in the scope of the controller that does a
window.alert
indicating if the 'isChecked' attribute of the controller's scope is checked or unchecked. (I'm doing awindow.alert
on purpose, because I want the execution to stop) - Bind that function into the directive
- In the "ng-change" of the checkbox of the directive trigger that function.
现在,当我们选中或取消选中我们得到警报的复选框,然后在警告我们可以看到,该指令的范围尚未更新。好了,人们会认为 NG-变化
被触发模型被更新之前,也正在显示警报,我们可以,根据在渲染文本见浏览器器isChecked具有两个范围的值相同。好吧,没什么大不了的,如果是这样的NG-变的行为,这样吧,我们总是可以设置一个 $观看
和运行功能有...但是让我们做另一项实验:
Now, when we check or uncheck the checkbox we get an alert, and in that alert we can see that the scope of the directive hasn't been updated yet. Ok, so one would think that the ng-change
gets triggered before the model gets updated, also while the alert is being displayed we can see that according to the text rendered in the browser "isChecked" has the same value in both scopes. All right, no big deal, if that's how the "ng-change" behave, so be it, we can always set a $watch
and run the function there... But lets do another experiment:
这样的:的 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>'
};
});
现在,我们只是使用指令范围的功能做到这一点的控制器范围的功能,在做同样的事情,但这次事实证明,该模型已被更新,所以它似乎在这一点上该指令的范围被更新,但不更新控制器的范围...怪异!
Now we are just using a function of the scope of the directive to do the same thing that the function of the scope of the controller was doing, but this time it turns out that the model has been updated, so it seems that at this point the scope of the directive is updated but the scope of the controller is not updated... Weird!
让我们确保是这样的话:的 PLUNKER 一> 的
Let's make sure that that's the case: 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>'
};
});
这一次,我们正在使用的指令范围的功能,触发控制器的约束功能,我们传递一个参数传递给控制器的功能与指令的范围值。现在,在控制器的功能,我们可以证实了我们在previous一步,这已经是怀疑:该分离范围首先得到更新,那么 NG-变化
变触发,它不是在那之后,该指令的范围的绑定得到更新。
This time we are using the function of the scope of the directive to trigger the bound function of the controller, and we are passing an argument to the controller's function with the value of the directive's scope. Now in the controller's function we can confirm what we already suspected in the previous step, which is: that the isolated scope gets updated first, then the ng-change
gets triggered, and it's not after that, that the bindings of the directive's scope get updated.
现在,终于,我的问题/ S:
Now, finally, my question/s:
- 不应该angularjs更新在同一时间的所有绑定属性,在做任何事情之前?
- 谁能给我什么内部发生的事情,为了证明这种行为的详细说明?
在换句话说:如果NG-变得到了触发模型被更新之前,我能理解,但我有一个非常很难理解,一个函数被更新模型后整理之前触发填充绑定属性的变化。
In other words: if the "ng-change" got triggered before the model gets updated, I could understand that, but I'm having a very hard time understanding that a function gets triggered after updating the model and before finishing to populate the changes of the bound properties.
如果你读到这里!祝贺和感谢您的耐心
If you read this far: congratulations and thanks for your patience!
何塞普
推荐答案
要总结问题, ngModelController
有一个过程要经过之前手表
将被解雇。你登录外 $范围
前财产 ngModelController
已处理的变化,引起了$消化周期,这将在转火 $观察家
。我不会考虑的模式
更新,直到这一点。
To summarize the problem, ngModelController
has a process to go through before watches
will be fired. You're logging the outer $scope
property before ngModelController
has processed the change and caused a $digest cycle, which would in turn fire $watchers
. I wouldn't consider the model
updated until that point.
这是一个复杂的系统。我做这个演示作为参考。我建议改变了收益
值,打字,和点击 - 只是梅辛与它周围的各种方式和检查日志。这使得它非常快速清除一切是如何工作的。
This is a complex system. I made this demo as a reference. I recommend changing the return
values, typing, and clicking - just messing around with it in all kinds of ways and checking the log. This makes it clear very quickly how everything works.
ngModelController
都有它自己的功能阵列,以不同的变化的反应上运行。
ngModelController
has it's own arrays of functions to run as responses to different changes.
ngModelController
有两种管道的决定如何处理一个样的变化做。这些允许开发以控制值的流动。
ngModelController
has two kinds of "pipelines" for determining what to do with a kind of change. These allow the developer to control the flow of values.
如果指定为 ngModel
的变化范围属性,则 $格式
管道将运行。这条管道是用来确定如何从 $范围来的值
应在视图中显示,但叶单模式。因此, NG-模式=富
和 $ scope.foo ='123'
,通常会显示 123
输入,但格式可以返回 1-2-3
或任何价值。 $ scope.foo
仍是123,但它显示为无论格式化返回。
If the scope property assigned as ngModel
changes, the $formatter
pipeline will run. This pipeline is used to determine how the value coming from $scope
should be displayed in the view, but leaves the model alone. So, ng-model="foo"
and $scope.foo = '123'
, would typically display 123
in the input, but the formatter could return 1-2-3
or any value. $scope.foo
is still 123, but it is displayed as whatever the formatter returned.
$解析器
处理同样的事情,但在相反。当用户键入的东西,在$解析器管道运行。无论一个 $解析器
的回报是什么将被设置为 ngModel。$ modelValue
。因此,如果用户键入 ABC
和 $解析器
收益 ABC
,那么该视图将不会改变,但 $ scope.foo
现在 ABC
。
$parsers
deal with the same thing, but in reverse. When the user types something, the $parser pipeline is run. Whatever a $parser
returns is what will be set to ngModel.$modelValue
. So, if the user types abc
and the $parser
returns a-b-c
, then the view won't change, but $scope.foo
now is a-b-c
.
在任何一个 $格式
或 $解析器
运行, $验证
将运行。无论属性名用于验证的有效性将通过验证函数(的返回值设置为真正
或假
)。
After either a $formatter
or $parser
runs, $validators
will be run. The validity of whatever property name is used for the validator will be set by the return value of the validation function (true
or false
).
$ viewChangeListeners
被解雇视图更改后,没有模型更改。因为我们指的是 $ scope.foo
和NOT ngModel。$ modelValue
这一个是特别令人困惑。视图将不可避免地更新 ngModel。$ modelValue
(除非在管道pvented $ P $),但事实并非模式的变革
我们指的是。基本上, $ viewChangeListeners
的 $解析器后被解雇
和NOT后 $格式化
。所以,当视图价值变动(用户类型), $解析器,$验证,那么$ viewChangeListeners
。乐倍= D
$viewChangeListeners
are fired after view changes, not model changes. This one is especially confusing because we're referring to $scope.foo
and NOT ngModel.$modelValue
. A view will inevitably update ngModel.$modelValue
(unless prevented in the pipeline), but that is not the model change
we're referring to. Basically, $viewChangeListeners
are fired after $parsers
and NOT after $formatters
. So, when the view value changes (user types), $parsers, $validators, then $viewChangeListeners
. Fun times =D
这一切都从 ngModelController
内部发生。在这个过程中, ngModel
对象不更新像你所期望的。管道被绕过,这将影响该对象的值。在该过程结束时, ngModel
的对象将用适当的 $ viewValue
和 $ modelValue
。
All of this happens internally from ngModelController
. During the process, the ngModel
object is not updated like you might expect. The pipeline is passing around values that will affect that object. At the end of the process, the ngModel
object will be updated with the proper $viewValue
and $modelValue
.
最后, ngModelController
完成和 $消化
周期将发生允许应用程序的其余部分响应所产生的变化。
Finally, the ngModelController
is done and a $digest
cycle will occur to allow the rest of the application to respond to the resulting changes.
下面是在什么情况下,演示了code应该发生的:
Here's the code from the demo in case anything should happen to it:
<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;
})
;
这篇关于在解释ngModel管道的顺序,解析器,格式化,viewChangeListeners和$观察家的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!