Angularjs form validation order

alecxe picture alecxe · Mar 19, 2014 · Viewed 7.4k times · Source

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.

Answer

Patrick picture Patrick · Mar 19, 2014

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/