Custom validators in AngularJS

This tutorial explains how to create custom validators in AngularJS 1.x.

AngularJS has some built-in validators that include email, number or required. It's incredibly useful the possibility we have to create custom validation rules for our applications.

To accomplish that we will use custom directives. The following checks if the input is a number and set the validity to false if the regular expression test fails. The validation is triggered on the element change event.

angular.module('myDirectives', []).directive('validNumber', function () {  
  return {
    require: 'ngModel',
    link: function (scope, el, attrs, ngModel) {
      el.bind('change', function () {
        scope.$apply(function () {
          ngModel.$setViewValue(el.val());
          ngModel.$render();

          var value = el[0].value;
          var re = /^\d+$/;

          if (!re.test(value)) {
            ngModel.$setValidity("number", false);
          } else {
            ngModel.$setValidity("number", true);
          }
        })
      })
    }
  }
});

The same code can be used for any kind of validation, we need just to change the regular expression or use any other logic based on the input we are validating.

The following is an example of a directive that does an email validation.

angular.module('myDirectives', []).directive('validEmail', function () {  
  return {
    require: 'ngModel',
    link: function (scope, el, attrs, ngModel) {
      el.bind('change', function () {
        scope.$apply(function () {
          ngModel.$setViewValue(el.val());
          ngModel.$render();

          var value = el[0].value;
          var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

          if (!re.test(value)) {
            ngModel.$setValidity("email", false);
          } else {
            ngModel.$setValidity("email", true);
          }
        })
      })
    }
  }
});

And below a directive that checks the size of an uploading file.

angular.module('myDirectives', []).directive('validFile', function () {  
  return {
    require: 'ngModel',
    link: function (scope, el, attrs, ngModel) {
      el.bind('change', function () {
        scope.$apply(function () {
          ngModel.$setViewValue(el.val());
          ngModel.$render();

          var file = el[0].files[0];

          if (file.size > 2000000) {
            ngModel.$setValidity("length", false);
          } else {
            ngModel.$setValidity("length", true);
          }
        })
      })
    }
  }
});

The next listing contains a form where we use our directives for validate numbers, emails and files size.

<form name="myForm" novalidate>  
  <div>
    <label for="myNumber">My Number:</label>
    <input name="myNumber" type="text" ng-model="myNumber" valid-number />
    <span ng-show="myForm.myNumber.$dirty && myForm.myNumber.$error.number">Number not valid</span>
  </div>
  <div>
    <label for="myEmail">My Email:</label>
    <input name="myEmail" type="text" ng-model="myEmail" valid-email />
    <span ng-show="myForm.myEmail.$dirty && myForm.myEmail.$error.email">Email not valid</span>
  </div>
  <div>
    <label for="myFile">My File:</label>
    <input name="myFile" type="file" ng-model="myFile" valid-file />
    <span ng-show="myForm.myFile.$error.length">File too big</span>
  </div>
  <div>
    <input type="submit" value="Send" />
  </div>
</form>  

The novalidate attribute in the form tag is new in HTML5 and specifies that the form data should not be validated when submitted.

Property $dirty is true if user has already interacted with the input.

$error is an object that contains references to controls or forms with failing validators, in our case number, email or length are added as reference when the validity is set to false.

comments powered by Disqus