Подтвердить что ты не робот

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 все еще недействительно. Идеи?

4b9b3361

Ответ 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);
        }
      };
    }
  };
});

Здесь плукер.