AngularJS > Forms & Validations

Custom validation directive in AngularJS

How to perform validation using custom directive in AngularJS?


In the previous post, we learnt how to delay model update in AngularJS, In this post we will learn how to validate the form using custom directive in AngularJS.

In below script block, we have created two custom directive, "adult" and "country".

Adult directive

The purpose of this custom directive is to ensure that user does not enter less than 18 and greater than 100 values in the control

In the first "adult" directive, we have made the ng-model directive mandatory using "require" keyword as we are going to perform validation based on model value of the input element.

Then we are using "link" keyword that is used to call a function for the directive. This link function defines APIs and functionality are necessary for the custom directive. This link funciton is executed for each instance of the directive.

This link function accepts four parameter by default and they are 

  • scope - the scope of the directive
  • element - the element that this directive attched to
  • attributes - a hash object with key-value pairs of normalized attribute (first character lower case and each consucative word name's first character in upper case) names.
  • control - the control (object) it is working with

    Inside the funciton, we are setting the "adult" directive to the default $validator object and calling a function that accepts modelValue and viewValue parameters. If the model value of the control is empty ($isEmpty), we are returning true (as we want empty text box treated as valid), otherwise we are converting the viewValue to number and then checking if its value is greater than 18 and less than 100 that returns true otherwise false. Returning false mark the $validator.adult as invalid and ultimately overall element on which "adult" directive is attached as invalid.
<script>
    var app = angular.module("app", []);

    app.directive('adult', function () {
        return {
            require: 'ngModel',
            link: function (scope, element, attributes, control) {
                control.$validators.adult = function (modelValue, viewValue) {

                    if (control.$isEmpty(modelValue)) // if empty, correct value
                    {
                        return true;
                    }
                    
                    var age = Number(viewValue);
                    
                    if (age >= 18 && age <= 100) // correct value
                    {
                        return true;
                    }
                    return false; // wrong value
                };
            }
        };
    });

    app.directive('country', function ($q, $timeout) {
        return {
            require: 'ngModel',
            link: function (scope, element, attributes, control) {
                // temporary data from local variable
                var countryNames = ['India', 'USA', 'Canada'];
                control.$asyncValidators.country = function (modelValue, viewValue) {
                    if (control.$isEmpty(modelValue)) {
                        return $q.when();
                    }
                    
                    var defer = $q.defer();

                    // to imitate the behavior of service access
                    // In real time, fire $http and get value then validate
                    $timeout(function () {
                        if (countryNames.indexOf(modelValue) === -1) // not found
                        {
                            defer.resolve(); // wow, this country available
                        }
                        else
                        {
                            defer.reject();
                        }
                    }, 1000);

                    return defer.promise;
                };
            }
        };
    });
</script>

Country directive

The purpose of this custom directive is to ensure that user doesn't enter the same country name that already exists in the database (in this case, JavaScript array).

In the country directive also, we have set ng-model directive as mandatory as we will be working on this value. As we are going to use $q (a service that help us to run function asynchronously and use their return value) and $timeout (a wrapper of window.setTimeout that helps us to execution a block of code after certain interval)  to  and $timeout Under the link funciton, for this demonstration we have taken array of country as "countryNames" against which we will validate. In this case, we have used $asyncValidators object that handles asynchrnous validation (generally used to make server side request).

Here also we are checking for empty model value, and then calling $q.when() that marks it valid. Then we are getting the $.defer() function that creates a deferred object that represents a task which will finish in future.

To imitate the behavior of the server side call, we are using $timeout funciton that delays the execution of the code inside. In this function we are checking for the modelValue and if not found, we return defer.reject() (kind of false) otherwise defer.resolve() (kind of true). The last 2nd parameter of $timeout is 1000 that instruct that this function should take 1 second time to respond. As said, this is just to imitate the behavior of server side call.

Ultimately, it returns defer.promise. Now based on whether $q.when(), $q.resolve() or $q.reject() function is called the defer.promise returns true and false.

HTML code

<div ng-app="app">
    <form novalidate name="myForm">
        Your age: <input type="number" name="txtAge" ng-model="age" adult />
        <span style="color:red;" ng-show="myForm.txtAge.$error.adult">Should be => 18 and <= 100</span>
        <hr />
        Country : <input type="text" name="txtCountry" ng-model="myCountry" country />
        <span style="color:red;" ng-show="myForm.txtCountry.$pending.country">Validating ....</span>
        <span style="color:brown;" ng-show="myForm.txtCountry.$error.country">Already exists, try another</span>
    </form>
</div>

In the HTML block, we Age text box with "adult" directive set and model as age. We also have a error message written with <span ng-show> that is shown based on whether myForm.txtAge.$error.adult returns true or false. The $validator.adult function defined in the "adult" directive returns true or false based on logic written.

In the Country text box, "country" directive is set. Based on the validation logic written in the country directive, $asyncValidators.country function returns true or false that sets  myForm.txtCountry.$pending.country and myForm.txtCountry.$error.country. As execution of the function defined is taking 1 second time, the <span> message of myForm.txtCountry.$pending.country will be shown (notice $pending) and when done, based on whether the function returns true or false myForm.txtCountry.$error.country <span> message is shown.

Output

Validation using custom directive in AngularJS

 Views: 3339 | Post Order: 56




Write for us