Как реализовать ng-изменение для пользовательской директивы
У меня есть директива с шаблоном вроде
<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
Ответы
Ответ 1
Если вам требуется ngModel
, вы можете просто вызвать $setViewValue
на ngModelController
, который неявно оценивает ng-change
. Четвертым параметром функции связывания должно быть ngModelCtrl. Следующий код заставит ng-change
работать для вашей директивы.
link : function(scope, element, attrs, ngModelCtrl){
scope.updateModel = function(item) {
ngModelCtrl.$setViewValue(item);
}
}
Чтобы ваше решение работало, удалите ngChange и ngModel из области выделения myDirective.
Здесь plunk: http://plnkr.co/edit/UefUzOo88MwOMkpgeX07?p=preview
Ответ 2
После некоторых исследований кажется, что наилучшим подходом является использование $timeout(callback, 0)
.
Он автоматически запускает цикл $digest
сразу после выполнения обратного вызова.
Итак, в моем случае решение заключалось в использовании
$timeout(scope.ngChange, 0);
Таким образом, не имеет значения, что является сигнатурой вашего обратного вызова, оно будет выполняться так же, как вы определили его в родительской области.
Вот plunkr с такими изменениями: http://plnkr.co/edit/9MGptJpSQslk8g8tD2bZ?p=preview
Ответ 3
TL;DR
По моему опыту вам просто нужно наследовать от 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-change
оценивается во время ngModelCtrl.$commitViewValue()
IF, ссылка на объект вашего ngModel изменилась. метод $commitViewValue()
автоматически вызывается $setViewValue(value, trigger)
, если вы не используете триггерный аргумент или не задали ngModelOptions.
Я указал, что ng-chage
будет автоматически запускаться , если изменена ссылка $viewValue
. Когда ваш ngModel
является string
или int
, вам не нужно беспокоиться об этом. Если ваш ngModel
является объектом и вы просто меняете некоторые его свойства, то $setViewValue
не будет eval ngChange
.
Если взять пример кода с начала сообщения
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
};
Ответ 4
Samuli Ulmanen и lucienBertin отвечают на это, хотя некоторое дальнейшее чтение в документации AngularJS дает дополнительные рекомендации относительно того, как с этим справиться (см. https://docs.angularjs.org/api/ng/type/ngModel.NgModelController).
В частности, в случаях, когда вы передаете объекты в $setViewValue (myObj). Документация AngularJS:
При использовании со стандартными входами значение представления всегда будет строкой (которая в некоторых случаях анализируется на другой тип, например объект Date для ввода [date].) Однако пользовательские элементы управления также могут передавать объекты для этого метод. В этом случае мы должны сделать копию объекта, прежде чем передать его в $setViewValue. Это связано с тем, что ngModel не выполняет глубокие наблюдения за объектами, он только ищет изменение идентичности. Если вы измените свойство объекта, тогда ngModel не поймет, что объект изменился и не будет ссылаться на конвейеры $parsers и $validators. По этой причине вы не должны изменять свойства копии после ее передачи в $setViewValue. В противном случае вы можете привести к неправильному изменению значения модели в области.
В моем конкретном случае моя модель представляет собой объект date date, поэтому я должен сначала клонировать объект, а затем вызывать setViewValue. Мне повезло, так как момент дает простой метод клонирования: 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);
}
};
}
Ответ 5
Основная проблема здесь заключается в том, что базовая модель не обновляется до тех пор, пока цикл digest, который произойдет после завершения 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