angular:验证多个依赖字段

时间:2013-09-08 09:50:13

标签: javascript validation angularjs

假设我有以下(非常简单的)数据结构:

$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的良好来源。

编辑:我想出了以下自定义指令(“share”是原始代码的“百分比”的同义词)。 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

如果我将Field 2降低到39,则Field 2现在有效,但Field 1仍然无效。想法?

5 个答案:

答案 0 :(得分:14)

好的,以下作品(再次,“分享”是“百分比”):

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”,并将值设置为您要观看的对象集。

答案 1 :(得分:8)

您可以查看angularui库(ui-utility部分)。它有ui-validate指令。

您可以实现它的一种方法是

<input type="number" name="accountNo" ng-model="account.percent"
ui-validate="{overflow : 'checkOverflow($value,account)' }">

在控制器上创建方法checkOverflow,该方法根据帐户计算返回true或false。

我自己没有尝试过,但想分享这个想法。阅读网站上的样本。

答案 2 :(得分:5)

我有一个案例,我有一个动态表单,我可以在我的表单上有可变数量的输入字段,我需要限制正在添加的输入控件的数量。

我不能轻易地限制这些输入字段的添加,因为它们是由其他因素的组合生成的,所以如果输入字段的数量超出限制,我需要使表单无效。我这样做是通过在我的控制器ctrl.myForm中创建对表单的引用,然后每次动态生成输入控件(在我的控制器代码中),我会进行限制检查,然后在表单上设置有效性像这样:ctrl.myForm.$setValidity("maxCount", false);

这很有效,因为验证不是由特定的输入字段决定的,而是我输入的总数。如果您需要通过多个字段的组合确定需要进行的验证,则可以使用相同的方法。

答案 3 :(得分:0)

为了我的理智

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
    }
  });

答案 4 :(得分:0)

您可以定义仅负责此检查的单个指令。

<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);
        }
      };
    }
  };
});

Here是一名掠夺者。