Angular: проверка нескольких зависимых полей
Скажем, у меня есть следующая (очень простая) структура данных:
$scope.accounts = [{
percent: 30,
name: "Checking"},
{ percent: 70,
name: "Savings"}];
Тогда у меня есть следующая структура как часть формы:
<div ng-repeat="account in accounts">
<input type="number" max="100" min="0" ng-model="account.percent" />
<input type="text" ng-model="account.name" />
</div>
Теперь я хочу проверить, что сумма процентов равна 100 для каждого набора учетных записей, но большинство примеров, которые я видел в пользовательских директивах, относятся только к проверке отдельного значения. Что такое идиоматический способ создания директивы, которая могла бы одновременно проверять несколько зависимых полей? В jquery существует множество решений для этого, но я не смог найти хороший источник для Angular.
EDIT: я придумал следующую настраиваемую директиву ( "share" - синоним исходного кода "percent" ).
Директива share-validate берет карту формы "{group: accounts, id: $index}"
как ее значение.
app.directive('shareValidate', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attr, ctrl) {
ctrl.$parsers.unshift(function(viewValue) {
params = angular.copy(scope.$eval(attr.shareValidate));
params.group.splice(params.id, 1);
var sum = +viewValue;
angular.forEach(params.group, function(entity, index) {
sum += +(entity.share);
});
ctrl.$setValidity('share', sum === 100);
return viewValue;
});
}
};
});
Этот ALMOST работает, но не может обрабатывать случай, когда поле является недействительным, но последующее изменение в другом поле делает его действительным снова. Например:
Field 1: 61
Field 2: 52
Если я возьму поле 2 до 39, поле 2 теперь будет действительным, но поле 1 все еще недействительно. Идеи?
Ответы
Ответ 1
Хорошо, следующие работы (опять же, "share" - "процент" ):
app.directive('shareValidate', function () {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attr, ctrl) {
scope.$watch(attr.shareValidate, function(newArr, oldArr) {
var sum = 0;
angular.forEach(newArr, function(entity, i) {
sum += entity.share;
});
if (sum === 100) {
ctrl.$setValidity('share', true);
scope.path.offers.invalidShares = false;
}
else {
ctrl.$setValidity('share', false);
scope.path.offers.invalidShares = true;
}
}, true); //enable deep dirty checking
}
};
});
В HTML установите атрибут как "share-validate" и значение для набора объектов, которые вы хотите просмотреть.
Ответ 2
Вы можете проверить библиотеку angularui (часть ui-utility). Он имеет директиву ui-validate.
Один из способов его реализации -
<input type="number" name="accountNo" ng-model="account.percent"
ui-validate="{overflow : 'checkOverflow($value,account)' }">
На контроллере создайте метод checkOverflow
, который возвращает true для false, основываясь на расчете счета.
Я не пробовал это сам, но хочу поделиться этой идеей. Прочтите образцы, присутствующие на сайте.
Ответ 3
У меня есть случай, когда у меня есть динамическая форма, где я могу иметь переменное количество полей ввода в моей форме, и мне нужно было ограничить количество добавляемых элементов управления вводом.
Я не мог легко ограничить добавление этих полей ввода, поскольку они были сгенерированы комбинацией других факторов, поэтому мне нужно было аннулировать форму, если количество полей ввода превысило предел. Я сделал это, создав ссылку на форму в моем контроллере ctrl.myForm
, а затем каждый раз, когда элементы управления ввода динамически генерируются (в моем коде контроллера), я бы выполнил проверку по лимиту, а затем установил правильность в форме, подобной этой: ctrl.myForm.$setValidity("maxCount", false);
Это хорошо работало, поскольку проверка не определялась конкретным полем ввода, а общим количеством моих входов. Этот же подход может работать, если у вас есть проверка, которая должна быть выполнена, которая определяется комбинацией нескольких полей.
Ответ 4
Для моего здравого смысла
HTML
<form ng-submit="applyDefaultDays()" name="daysForm" ng-controller="DaysCtrl">
<div class="form-group">
<label for="startDate">Start Date</label>
<div class="input-group">
<input id="startDate"
ng-change="runAllValidators()"
ng-model="startDate"
type="text"
class="form-control"
name="startDate"
placeholder="mm/dd/yyyy"
ng-required
/>
</div>
</div>
<div class="form-group">
<label for="eEndDate">End Date</label>
<div class="input-group">
<input id="endDate"
ng-change="runAllValidators()"
ng-model="endDate"
type="text"
class="form-control"
name="endDate"
placeholder="mm/dd/yyyy"
ng-required
/>
</div>
</div>
<div class="text-right">
<button ng-disabled="daysForm.$invalid" type="submit" class="btn btn-default">Apply Default Dates</button>
</div>
JS
'use strict';
angular.module('myModule')
.controller('DaysCtrl', function($scope, $timeout) {
$scope.initDate = new Date();
$scope.startDate = angular.copy($scope.initDate);
$scope.endDate = angular.copy($scope.startDate);
$scope.endDate.setTime($scope.endDate.getTime() + 6*24*60*60*1000);
$scope.$watch("daysForm", function(){
//fields are only populated after controller is initialized
$timeout(function(){
//not all viewalues are set yet for somereason, timeout needed
$scope.daysForm.startDate.$validators.checkAgainst = function(){
$scope.daysForm.startDate.$setDirty();
return (new Date($scope.daysForm.startDate.$viewValue)).getTime() <=
(new Date($scope.daysForm.endDate.$viewValue)).getTime();
};
$scope.daysForm.endDate.$validators.checkAgainst = function(){
$scope.daysForm.endDate.$setDirty();
return (new Date($scope.daysForm.startDate.$viewValue)).getTime() <=
(new Date($scope.daysForm.endDate.$viewValue)).getTime();
};
});
});
$scope.runAllValidators = function(){
//need to run all validators on change
$scope.daysForm.startDate.$validate();
$scope.daysForm.endDate.$validate();
};
$scope.applyDefaultDays = function(){
//do stuff
}
});
Ответ 5
Вы можете определить одну директиву, которая отвечает только за эту проверку.
<form>
<div ng-repeat="account in accounts">
<input type="number" max="100" min="0" ng-model="account.percent" />
<input type="text" ng-model="account.name" />
</div>
<!-- HERE IT IS -->
<sum-up-to-hundred accounts="accounts"></sum-up-to-hundred>
</form>
И вот простой директивный код.
app.directive('sumUpToHundred', function() {
return {
scope: {
accounts: '<'
},
require: {
formCtrl: '^form'
},
bindToController: true,
controllerAs: '$ctrl',
controller: function() {
var vm = this;
vm.$doCheck = function(changes) {
var sum = vm.accounts.map((a)=> a.percent).reduce((total, n)=> total + n);
if (sum !== 100) {
vm.formCtrl.$setValidity('sumuptohundred', false);
} else {
vm.formCtrl.$setValidity('sumuptohundred', true);
}
};
}
};
});
Здесь плукер.