首页 文章

如何向AngularJS表单添加自定义验证?

提问于
浏览
272

我有一个带有输入字段和验证设置的表单,通过添加 required 属性等 . 但对于某些领域,我需要做一些额外的验证 . 我如何"tap in"进行 FormController 控制的验证?

自定义验证可能类似于“如果填写了这3个字段,则此字段是必需的,需要以特定方式进行格式化” .

FormController.$setValidity 中有一个方法,但看起来不像公共API,所以我宁愿不使用它 . 创建自定义指令并使用 NgModelController 看起来像另一个选项,但基本上要求我为每个自定义验证规则创建一个指令,这是我不想要的 .

实际上,将控制器中的字段标记为无效(同时保持 FormController 同步)可能是我在最简单的场景中完成工作所需要的,但我不知道该怎么做 .

12 回答

  • 0

    Edit: added information about ngMessages (>= 1.3.X) below.

    标准表格验证消息(1.0.X及以上)

    由于这是Google“Angular Form Validation”的最佳结果之一,目前,我想为来自那里的任何人添加另一个答案 .

    FormController中有一个方法 . $ setValidity但看起来不像公共API,所以我宁愿不使用它 .

    这是“公开的”,不用担心 . 用它 . 这就是它的用途 . 如果不打算使用它,Angular开发人员会将其私有化 .

    要进行自定义验证,如果您不想将Angular-UI用作建议的其他答案,则只需滚动自己的验证指令即可 .

    app.directive('blacklist', function (){ 
       return {
          require: 'ngModel',
          link: function(scope, elem, attr, ngModel) {
              var blacklist = attr.blacklist.split(',');
    
              //For DOM -> model validation
              ngModel.$parsers.unshift(function(value) {
                 var valid = blacklist.indexOf(value) === -1;
                 ngModel.$setValidity('blacklist', valid);
                 return valid ? value : undefined;
              });
    
              //For model -> DOM validation
              ngModel.$formatters.unshift(function(value) {
                 ngModel.$setValidity('blacklist', blacklist.indexOf(value) === -1);
                 return value;
              });
          }
       };
    });
    

    以下是一些示例用法:

    <form name="myForm" ng-submit="doSomething()">
       <input type="text" name="fruitName" ng-model="data.fruitName" blacklist="coconuts,bananas,pears" required/>
       <span ng-show="myForm.fruitName.$error.blacklist">
          The phrase "{{data.fruitName}}" is blacklisted</span>
       <span ng-show="myForm.fruitName.$error.required">required</span>
       <button type="submit" ng-disabled="myForm.$invalid">Submit</button>
    </form>
    

    Note: in 1.2.X it's probably preferrable to substitute ng-if for ng-show above

    这是强制性的plunker link

    另外,我已经写了一些关于这个主题的博客文章更详细一些:

    Angular Form Validation

    Custom Validation Directives

    编辑:在1.3.X中使用ngMessages

    您现在可以使用ngMessages模块而不是ngShow来显示错误消息 . 它实际上可以处理任何事情,它不一定是一个错误信息,但这里是基础知识:

    • 包括 <script src="angular-messages.js"></script>

    • 参考模块声明中的 ngMessages

    var app = angular.module('myApp', ['ngMessages']);
    
    • 添加适当的标记:
    <form name="personForm">
      <input type="email" name="email" ng-model="person.email" required/>
    
      <div ng-messages="personForm.email.$error">
        <div ng-message="required">required</div>
        <div ng-message="email">invalid email</div>
      </div>
    </form>
    

    在上面的标记中, ng-message="personForm.email.$error" 基本上指定了 ng-message 子指令的上下文 . 然后 ng-message="required"ng-message="email" 指定要监视的上下文的属性 . Most importantly, they also specify an order to check them in . 它在列表中找到的第一个是"truthy"获胜,它将显示该消息而不显示其他消息 .

    plunker for the ngMessages example

  • 4

    Angular-UI的项目包含一个ui-validate指令,它可能会帮助你解决这个问题 . 它让你指定一个函数来调用来进行验证 .

    查看演示页面:http://angular-ui.github.com/,向下搜索Validate Headers .

    从演示页面:

    <input ng-model="email" ui-validate='{blacklist : notBlackListed}'>
    <span ng-show='form.email.$error.blacklist'>This e-mail is black-listed!</span>
    

    然后在你的控制器中:

    function ValidateCtrl($scope) {
      $scope.blackList = ['bad@domain.com','verybad@domain.com'];
      $scope.notBlackListed = function(value) {
        return $scope.blackList.indexOf(value) === -1;
      };
    }
    
  • 362

    您可以在验证方案中使用ng-required(“如果填写了这3个字段,则需要此字段”:

    <div ng-app>
        <input type="text" ng-model="field1" placeholder="Field1">
        <input type="text" ng-model="field2" placeholder="Field2">
        <input type="text" ng-model="field3" placeholder="Field3">
        <input type="text" ng-model="dependentField" placeholder="Custom validation"
            ng-required="field1 && field2 && field3">
    </div>
    
  • 1

    你可以使用Angular-Validator .

    Example: using a function to validate a field

    <input  type = "text"
        name = "firstName"
        ng-model = "person.firstName"
        validator = "myCustomValidationFunction(form.firstName)">
    

    然后在你的控制器中你会有类似的东西

    $scope.myCustomValidationFunction = function(firstName){ 
       if ( firstName === "John") {
           return true;
        }
    

    You can also do something like this:

    <input  type = "text"
            name = "firstName"
            ng-model = "person.firstName"
            validator = "'!(field1 && field2 && field3)'"
            invalid-message = "'This field is required'">
    

    (其中field1 field2和field3是范围变量 . 您可能还想检查字段是否不等于空字符串)

    如果该字段未通过 validator ,则该字段将被标记为无效,用户将无法提交该表单 .

    有关更多用例和示例,请参阅:https://github.com/turinggroup/angular-validator

    免责声明:我是Angular-Validator的作者

  • 44

    这是在表单中进行自定义通配符表达式验证的一种很酷的方法(来自:Advanced form validation with AngularJS and filters):

    <form novalidate="">  
       <input type="text" id="name" name="name" ng-model="newPerson.name"
          ensure-expression="(persons | filter:{name: newPerson.name}:true).length !== 1">
       <!-- or in your case:-->
       <input type="text" id="fruitName" name="fruitName" ng-model="data.fruitName"
          ensure-expression="(blacklist | filter:{fruitName: data.fruitName}:true).length !== 1">
    </form>
    
    app.directive('ensureExpression', ['$http', '$parse', function($http, $parse) {
        return {
            require: 'ngModel',
            link: function(scope, ele, attrs, ngModelController) {
                scope.$watch(attrs.ngModel, function(value) {
                    var booleanResult = $parse(attrs.ensureExpression)(scope);
                    ngModelController.$setValidity('expression', booleanResult);
                });
            }
        };
    }]);
    

    jsFiddle demo(支持表达式命名和多个表达式)

    它类似于 ui-validate ,但你不需要这样做ui.utils .

  • 3

    我最近创建了一个指令,允许基于表达式的角形式输入失效 . 可以使用任何有效的角度表达式,它使用对象表示法支持自定义验证密钥 . 用角度v1.3.8测试

    .directive('invalidIf', [function () {
            return {
                require: 'ngModel',
                link: function (scope, elm, attrs, ctrl) {
    
                    var argsObject = scope.$eval(attrs.invalidIf);
    
                    if (!angular.isObject(argsObject)) {
                        argsObject = { invalidIf: attrs.invalidIf };
                    }
    
                    for (var validationKey in argsObject) {
                        scope.$watch(argsObject[validationKey], function (newVal) {
                            ctrl.$setValidity(validationKey, !newVal);
                        });
                    }
                }
            };
        }]);
    

    你可以像这样使用它:

    <input ng-model="foo" invalid-if="{fooIsGreaterThanBar: 'foo > bar',
                                       fooEqualsSomeFuncResult: 'foo == someFuncResult()'}/>
    

    或者只是传入一个表达式(它将被赋予默认的validationKey“invalidIf”)

    <input ng-model="foo" invalid-if="foo > bar"/>
    
  • 91

    @synergetic我认为@blesh假设将函数验证如下所示

    function validate(value) {
        var valid = blacklist.indexOf(value) === -1;
        ngModel.$setValidity('blacklist', valid);
        return valid ? value : undefined;
    }
    
    ngModel.$formatters.unshift(validate);
    ngModel.$parsers.unshift(validate);
    
  • 12

    Update:

    改进和简化的先前指令版本(一个而不是两个)具有相同的功能:

    .directive('myTestExpression', ['$parse', function ($parse) {
        return {
            restrict: 'A',
            require: 'ngModel',
            link: function (scope, element, attrs, ctrl) {
                var expr = attrs.myTestExpression;
                var watches = attrs.myTestExpressionWatch;
    
                ctrl.$validators.mytestexpression = function (modelValue, viewValue) {
                    return expr == undefined || (angular.isString(expr) && expr.length < 1) || $parse(expr)(scope, { $model: modelValue, $view: viewValue }) === true;
                };
    
                if (angular.isString(watches)) {
                    angular.forEach(watches.split(",").filter(function (n) { return !!n; }), function (n) {
                        scope.$watch(n, function () {
                            ctrl.$validate();
                        });
                    });
                }
            }
        };
    }])
    

    用法示例:

    <input ng-model="price1" 
           my-test-expression="$model > 0" 
           my-test-expression-watch="price2,someOtherWatchedPrice" />
    <input ng-model="price2" 
           my-test-expression="$model > 10" 
           my-test-expression-watch="price1" 
           required />
    

    结果:相互依赖的测试表达式,其中验证器在其他的更改时执行指令模型和当前模型 .

    测试表达式具有本地 $model 变量,您应该使用该变量将其与其他变量进行比较 .

    Previously:

    我试图通过添加额外的指令来改进@Plantface代码 . 如果在多个ngModel变量中进行更改时需要执行表达式,则此额外指令非常有用 .

    .directive('ensureExpression', ['$parse', function($parse) {
        return {
            restrict: 'A',
            require: 'ngModel',
            controller: function () { },
            scope: true,
            link: function (scope, element, attrs, ngModelCtrl) {
                scope.validate = function () {
                    var booleanResult = $parse(attrs.ensureExpression)(scope);
                    ngModelCtrl.$setValidity('expression', booleanResult);
                };
    
                scope.$watch(attrs.ngModel, function(value) {
                    scope.validate();
                });
            }
        };
    }])
    
    .directive('ensureWatch', ['$parse', function ($parse) {
        return {
            restrict: 'A',
            require: 'ensureExpression',
            link: function (scope, element, attrs, ctrl) {
                angular.forEach(attrs.ensureWatch.split(",").filter(function (n) { return !!n; }), function (n) {
                    scope.$watch(n, function () {
                        scope.validate();
                    });
                });
            }
        };
    }])
    

    示例如何使用它来创建交叉验证字段:

    <input name="price1"
           ng-model="price1" 
           ensure-expression="price1 > price2" 
           ensure-watch="price2" />
    <input name="price2" 
           ng-model="price2" 
           ensure-expression="price2 > price3" 
           ensure-watch="price3" />
    <input name="price3" 
           ng-model="price3" 
           ensure-expression="price3 > price1 && price3 > price2" 
           ensure-watch="price1,price2" />
    

    执行 ensure-expression 以在 ng-model 或任何 ensure-watch 变量更改时验证模型 .

  • 11

    在AngularJS中,定义自定义验证的最佳位置是Cutsom指令 . AngularJS提供ngMessages模块 .

    ngMessages是一个指令,用于根据侦听的键/值对象的状态显示和隐藏消息 . 该指令本身补充了使用ngModel $ error对象(存储验证错误的键/值状态)的错误消息报告 .

    对于自定义表单验证应该使用带有自定义指令的ngMessages模块 . 这里我有一个简单的验证,它将检查数字长度是否小于6在屏幕上显示错误

    <form name="myform" novalidate>
                    <table>
                        <tr>
                            <td><input name='test' type='text' required  ng-model='test' custom-validation></td>
                            <td ng-messages="myform.test.$error"><span ng-message="invalidshrt">Too Short</span></td>
                        </tr>
                    </table>
                </form>
    

    以下是如何创建自定义验证指令

    angular.module('myApp',['ngMessages']);
            angular.module('myApp',['ngMessages']).directive('customValidation',function(){
                return{
                restrict:'A',
                require: 'ngModel',
                link:function (scope, element, attr, ctrl) {// 4th argument contain model information 
    
                function validationError(value) // you can use any function and parameter name 
                    {
                     if (value.length > 6) // if model length is greater then 6 it is valide state
                     {
                     ctrl.$setValidity('invalidshrt',true);
                     }
                     else
                     {
                     ctrl.$setValidity('invalidshrt',false) //if less then 6 is invalide
                     }
    
                     return value; //return to display  error 
                    }
                    ctrl.$parsers.push(validationError); //parsers change how view values will be saved in the model
                }
                };
            });
    

    $setValidity 是内置函数,用于将模型状态设置为有效/无效

  • 1

    我扩展了@Ben Lesh的答案,能够指定验证是否区分大小写(默认)

    使用:

    <input type="text" name="fruitName" ng-model="data.fruitName" blacklist="Coconuts,Bananas,Pears" caseSensitive="true" required/>
    

    码:

    angular.module('crm.directives', []).
    directive('blacklist', [
        function () {
            return {
                restrict: 'A',
                require: 'ngModel',
                scope: {
                    'blacklist': '=',
                },
                link: function ($scope, $elem, $attrs, modelCtrl) {
    
                    var check = function (value) {
                        if (!$attrs.casesensitive) {
                            value = (value && value.toUpperCase) ? value.toUpperCase() : value;
    
                            $scope.blacklist = _.map($scope.blacklist, function (item) {
                                return (item.toUpperCase) ? item.toUpperCase() : item
                            })
                        }
    
                        return !_.isArray($scope.blacklist) || $scope.blacklist.indexOf(value) === -1;
                    }
    
                    //For DOM -> model validation
                    modelCtrl.$parsers.unshift(function (value) {
                        var valid = check(value);
                        modelCtrl.$setValidity('blacklist', valid);
    
                        return value;
                    });
                    //For model -> DOM validation
                    modelCtrl.$formatters.unshift(function (value) {
                        modelCtrl.$setValidity('blacklist', check(value));
                        return value;
                    });
                }
            };
        }
    ]);
    
  • 4

    调用服务器的自定义验证

    使用处理异步验证的ngModelController $asyncValidators API,例如向后端发出 $http 请求 . 添加到对象的函数必须返回一个必须在有效时解析或在无效时被拒绝的promise . 正在进行的异步验证按 ngModelController.$pending 中的密钥存储 . 有关更多信息,请参阅AngularJS Developer Guide - Forms (Custom Validation) .

    ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) {
      var value = modelValue || viewValue;
    
      // Lookup user by username
      return $http.get('/api/users/' + value).
         then(function resolved() {
           //username exists, this means validation fails
           return $q.reject('exists');
         }, function rejected() {
           //username does not exist, therefore this validation passes
           return true;
         });
    };
    

    有关更多信息,请参阅


    使用$ validators API

    接受的答案使用 $parsers$formatters 管道添加自定义同步验证程序 . AngularJS 1.3添加了 $validators API,因此无需在 $parsers$formatters 管道中放置验证器:

    app.directive('blacklist', function (){ 
       return {
          require: 'ngModel',
          link: function(scope, elem, attr, ngModel) {           
              ngModel.$validators.blacklist = function(modelValue, viewValue) {
                  var blacklist = attr.blacklist.split(',');
                  var value = modelValue || viewValue;
                  var valid = blacklist.indexOf(value) === -1;
                  return valid;
              });    
          }
       };
    });
    

    有关更多信息,请参阅AngularJS ngModelController API Reference - $validators .

  • 28

    这个帖子中提供了一些很好的例子和库,但是它们并没有我想要的东西 . 我的方法:angular-validity - 一个基于promise的验证库,用于异步验证,带有可选的Bootstrap样式 .

    OP用例的角度有效性解决方案可能如下所示:

    <input  type="text" name="field4" ng-model="field4"
            validity="eval"
            validity-eval="!(field1 && field2 && field3 && !field4)"
            validity-message-eval="This field is required">
    

    这是一个Fiddle,如果你想把它旋转一下 . lib可在GitHub上找到,有详细的文档和大量的现场演示 .

相关问题