Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Angularjs form validation order

I have a simple html form containing regular text input. ng-minlength, ng-maxlength and ng-pattern angular built-in form input directives are set on the input.

Problem: ng-pattern check is applied before the length check by ng-minlength and ng-maxlength.

Question: how can I change the default check order: i.e. first check for the length, then apply pattern check?

Example:

<body ng-app>

    <div>
        <form name="myForm">
            Name: <input name="name" type="text" ng-model="name" ng-minlength="3" ng-maxlength="16" ng-pattern="/^\w+$/"/>
            <div ng-show="myForm.name.$dirty && myForm.name.$invalid">
                <span ng-show="myForm.name.$error.pattern">Pattern error</span>
                <span ng-show="myForm.name.$error.minlength || myForm.name.$error.maxlength">Length error</span>
            </div>
            <br/>
            <input type="submit" value="Submit">
        </form>
    </div>

</body>

Current behavior:

  • enter "#" - see "Pattern error"
  • enter "###" - see "Pattern error"

Desired behavior:

  • enter "#" - see "Length error"
  • enter "###" - see "Pattern error"

FYI, related jsfiddle.

Thanks in advance.

like image 789
alecxe Avatar asked Mar 19 '14 16:03

alecxe


2 Answers

Write your own directive:

var mod = angular.module("myApp", []);

mod.directive("nameValidation", function () {
    return {
        restrict: "A",
        require: "ngModel",
        link: function (scope, element, attrs, ngModelCtrl) {
            var validate = function (value) {
                var minLen = parseInt(attrs.myMinlength, 10),
                    maxLen = parseInt(attrs.myMaxlength, 10),
                    pattern = attrs.myPattern,
                    match = pattern.match(/^\/(.*)\/([gim]*)$/),
                    lenErr = false;

                if (match) {
                    pattern = new RegExp(match[1], match[2]);   
                }
                if (!ngModelCtrl.$isEmpty(value)) {
                    ngModelCtrl.$setValidity("pattern", true);
                    if ((minLen && value.length < minLen) || (maxLen && value.length > maxLen)) {
                        ngModelCtrl.$setValidity("length", false);
                        lenErr = true;
                    }
                    else {
                        ngModelCtrl.$setValidity("length", true);
                        lenErr = false;
                    }

                    if (!lenErr) {
                        if (match && !pattern.test(value)) {
                            ngModelCtrl.$setValidity("pattern", false);
                        }
                        else {
                            ngModelCtrl.$setValidity("pattern", true);
                        }
                    }
                }
                else {
                    ngModelCtrl.$setValidity("length", true);
                    ngModelCtrl.$setValidity("pattern", true);
                }
            }

            ngModelCtrl.$parsers.push(validate);
            ngModelCtrl.$formatters.push(validate);
        }
    }
});

Then in your HTML, include the app and use the directive:

<body ng-app="myApp">

<div>
    <form name="myForm">
        Name: <input name="name" type="text" ng-model="name" name-validation="" my-minlength="3" my-maxlength="16" my-pattern="/^\w+$/"/>
        <div ng-show="myForm.name.$dirty && myForm.name.$invalid">
            <span ng-show="myForm.name.$error.pattern">Pattern error</span>
            <span ng-show="myForm.name.$error.length">Length error</span>
        </div>
        <br/>
        <input type="submit" value="Submit">
    </form>
</div>
</body>

The directive uses my-minlength, my-maxlength, and my-pattern for the three values. If length fails, that will trip first. If not, then pattern will show as error if it doesn't match. Consider renaming this directive if you want to use it other places besides name as minlength, maxlength, and pattern can be passed to it via attributes. If they are left off, they will be ignored.

See jsfiddle: http://jsfiddle.net/4zpxk/6/

like image 160
Patrick Avatar answered Sep 28 '22 09:09

Patrick


I searched in angular code why this behavior. Then in the function 'textInputType' that it's the specific function that handles text inputs for the angular 'input' directive I found this at the end of this function, where we can see three blocks of code.

// pattern validator
if (pattern){
 //validator logic
}

// min length validator
if (attr.ngMinlength) {
 //validator logic
}

 // max length validator
if (attr.ngMaxlength) {
 //validator logic
}

So, no matter if you change the declaration order of your ng-* attributes in the html input element you will always get same result but if you change the order of the blocks, I mean, put the min length validator block before pattern validator block you will have the result that you expect.

This is a solution for your problem but you have to make a litte change in angular code and I don't know if you really like this. But you got a very common situation where order of the declaration of validation concepts matters, so, something more must be done to handle this. Thanks

like image 36
sergio Avatar answered Sep 28 '22 09:09

sergio