Объяснение порядка конвейера ngModel, синтаксических анализаторов, форматеров, viewChangeListeners и $ watchers
этот вопрос нелегко сформулировать, поэтому я попытаюсь объяснить, что я хочу знать, на примере:
рассмотрим этот простой 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>'
};
});
этот 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>
приложение:
- имеет один контроллер с именем: "mainCtrl", где мы определили переменную области, называемую"isChecked"
- она также имеет одну директиву под названием "testDirective "с изолированной областью и свойством привязки под названием"isChecked".
- и в html мы создаем экземпляр " testDirective "внутри" mainCtrl "и связываем свойство" isChecked "области" mainCtrl "со свойством" isChecked " изолированной области директивы.
- директива отображает флажок, который имеет свойство "isChecked" scope в качестве модели.
- когда мы проверяем или снимаем флажок, мы видим, что оба свойства обеих областей обновляются одновременно.
пока все хорошо.
теперь давайте немного изменимся, вот так: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>
единственное что мы сделали-это:
- определите функцию в области контроллера, которая выполняет
window.alert
указывает, является ли атрибут "isChecked" области контроллера checked или unchecked. (Я делаю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
запускается, и это не после этого, что привязки области директивы обновляются.
теперь, наконец, мой вопрос/s:
- не стоит angularjs обновляет все связанные свойства одновременно, прежде чем делать что-либо еще?
другими словами: если" ng-change " был запущен до обновления модели, я мог бы это понять, но мне очень сложно понять, что функция запускается после обновления модели и до завершения заполнения изменений связанных свойств.
если Вы читаете это далеко: поздравления и спасибо за ваше терпение!
Хосеп
1 ответов
резюмируя проблему,ngModelController
имеет процесс, чтобы пройти до watches
будет уволен. Вы регистрируете внешний $scope
свойства перед ngModelController
обработал изменение и вызвал цикл $ digest, который, в свою очередь, уволил $watchers
. Я бы не стал рассматривать model
обновлено до этого момента.
это сложная система. Я сделал эту демонстрацию в качестве ссылки. Я рекомендую изменить return
значения, ввод и нажатие-просто возиться с ним во всех видах способы и проверка журнала. Это позволяет очень быстро понять, как все работает.
Demo (получайте удовольствие!)
ngModelController
имеет собственные массивы функций для запуска в качестве ответов на различные изменения.
ngModelController
имеет два вида "трубопроводов" для определения того, что делать с каким-либо изменением. Они позволяют разработчику управлять потоком значений.
если свойство scope назначено как ngModel
изменений будет работать. Этот конвейер используется для определения того, как значение исходит из $scope
должно отображаться в представлении, но оставляет модель в покое. Итак,ng-model="foo"
и $scope.foo = '123'
, обычно отображается 123
на входе, но форматер может вернуться 1-2-3
или какую-либо ценность. $scope.foo
по-прежнему 123, но он отображается как независимо от того, что вернул форматер.
$parsers
дело с тем же самым, но в обратном порядке. Когда пользователь вводит что-то, запускается конвейер $ parser. Что $parser
returns-это то, что будет установлено в ngModel.$modelValue
. Итак, если пользователь вводит abc
и $parser
возвращает a-b-c
, то вид не изменится, но $scope.foo
сейчас a-b-c
.
после $formatter
или $parser
работает, $validators
будет работать. Действительность любого имени свойства, используемого для валидатора, будет установлена возвращаемым значением функции проверки (true
или false
).
$viewChangeListeners
запускаются после изменения вида, а не изменения модели. Это особенно запутанно, потому что мы имеем в виду $scope.foo
, а не ngModel.$modelValue
. Представление неизбежно обновится ngModel.$modelValue
(если не предотвращено в трубопроводе), но это не model change
мы имеем в виду. В основном, $viewChangeListeners
уволен после $parsers
и не позднее $formatters
. Итак, когда изменяется значение представления (типы пользователей),$parsers, $validators, then $viewChangeListeners
. Веселые времена =D
все это происходит внутри ngModelController
. Во время процесса ngModel
объект не обновляется, как вы могли бы ожидать. Конвейер передает значения, которые будут влиять на этот объект. В конце процесса,ngModel
объект будет обновлен с помощью правильного $viewValue
и $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;
})
;