AngularJS

From Zero to Hero: AngularJS Validation

Posted on

Today we’re going to talk about the front-end validation using AngularJS and Bootstrap. This will be illustrated by four separate examples:

–       usage of AngularJS build-in validation directives;

–       custom validation of a bank account with the AngularJS service;

–       username validation done after the onblur event;

–       group field validation.

Preparing a good stock

Like in cooking we need a good stock that will serve as our foundation for development.

Bower

What is “Bower” (https://github.com/bower/bower)? It is a front-end package manager. You can use it for the installation (download) of a specific package into your project, for example:

bower install bootstrap
bower install jquery
bower install angular

All installed libraries are then stored inside the folder /bower_components. Even your project can be defined as Bower Package by using the init command:

 bower init

As a result, your project dependency list will be created inside the bower.json file, which looks like this:

"dependencies": {
"angular": "~1.2.3",
    "bootstrap": "~3.0.2"
  }
}

So when you’re going to pack the whole project and push it for example to the GitHub, you can push it without dependencies (packages). If anyone wants to use this project (after obtaining it from GitHub), then one should only run the following command:

bower install

This will start downloading all necessary dependencies defined in bower.json.

Another useful feature is the registration of your (GitHub) project as a bower package. That means that everyone can use the command:

bower install your_project

Your project is then installed automatically with all dependencies defined in bower.json.

Bootstrap

The next ingredient in our stock is Bootstrap. It is a front-end framework for responsive web development. In our case we will need its advanced grid system and CSS styling. Basically, it means that we’ll use a prepared set of appropriately styled HTML elements (http://getbootstrap.com/css/) for the form usage.

We are going to use the so-called Horizontal Form that uses predefined grid classes.

<form class=”form-horizontal”>
  <div class=”form-group | has-error | has-success”>
    <label for=”inputEmail” class=”col-sm-2 control-label”>Email: *</label>
    <div class=”col-sm-10”>
      <input type=”email” class=”form-control” id=”inputEmail”>
      <span class="help-block">Validation Error Message</span>
    </div>
  </div>
  <div class=”form-group>
    <div class=”col-sm-offset-2 col-sm-10”>
      <button type=”submit” class=”btn btn-default”>Submit</button>
    </div>
  </div>
</form>

The class form-group acts as a grid row, so there is no need to use a standard Bootstrap .row class.

We can also add the class has-error or has-success – depends on the result of validation.

Inside a row we are using two columns for positioning: one with the class col-sm-2 and one with the class col-sm-10. The first column is used for the input label with the class control-label whereas the second is used for the input itself and with the class form-control.

For the position of the submit button an offset is used (class col-sm-offset-2) and classes btn btn-default are used for button styling.

For any kind of messages (for example validation messages) that are bound to the input, a span with the class help-block is used.

Directive controllers

How can directives work together i.e. speak to each other? The answer is through a directive controller – this is a function assigned to the controller property of the directive. We can say that the directive controller represents its API to the other directives. We use the require property of the directive to gain a controller and then it is accessible as the last parameter in the link function of the directive:

require: “passwordVerify”

where “passwordVerify” is the name of the directive whose controller we want to access. How to assemble a name for the required property is demonstrated in the book: Brad Green & Shyam Seshadri, AngularJS, page 134, table 6.7.

We can also access more controllers at once using the bracket notation:

require: ["passwordVerify", "ngModel"]

In the latter case, the array of controllers is passed to the link function as a parameter and then we can access a specific controller in a usual way (ctrl[0], ctrl[1] etc.).

But why do we talk about directive controllers in the context of validation ? Because in the custom directive (used for validation) we are going to speak to the directive ng-model and its controller NgModelController. The reason hides in the method $setValidity(..) which is called to mark a validation error.

Isolated Scope

Because the validation in AngularJS is done with directives, we have to understand how scope works in relation to them. Usually, you want to access the scope to watch model values etc. There are three options of getting a scope in the directive using the scope property of the directive.

By default you get an existing scope (scope: false) from your directive’s DOM element. You can also get a new scope (scope: true) and you can also get an isolated scope (scope: {..}), which is usually used with reusable components and validation.

In case of an isolated scope you can pass data from and to parent scope based on the binding strategies. Let’s look at an example.

<body ng-app="exampleModule">
<form class="form-horizontal" ng-controller="exampleController">
    <div class="form-group">
       <label class="col-sm-2 control-label" for="inputExample">Some label: *</label>
       <div class="col-sm-10">
           <input required type="text" name="name" id="inputExample" 
               class="form-control"
               ng-model="message"
               my-message-as-expr-param="getMessage(param1)"
               my-message-as-expr="getMessage()"
               my-message-as-string="{{message}}"
               my-message="message" >
       </div>
   </div>
</form>

In the above section we see custom directives all starting with “my-message“. We are going to show how to pass data from and to the parent scope (scope of an input element).

First of all we define an Angular controller, its scope variable message and two scope methods getMessage() and getMessage(param) – the first one returns a message and with the second one a message is changed to an upper case.

var exampleModule = angular.module("exampleModule", []);
exampleModule.controller("exampleController", ['$scope', function ($scope) {
    $scope.message = 'example message';
    
    $scope.getMessage = function() {
        return $scope.message;
    }
    $scope.getMessage = function(param) {
        if(param) return $scope.message.toUpperCase();
        return $scope.message;
    }
}]);

Now we have to create a myMessage (my-message) directive:

exampleModule.directive("myMessage", ['$parse', function($parse) {
  return {
    require: "ngModel",
    scope: {
        myMessage: '=myMessage',
        myMessageAsString: '@myMessageAsString',
        myMessageAsExpr: '&myMessageAsExpr',
        myMessageAsExprParam: '&myMessageAsExprParam'
    },
    link: function(scope, element, attrs, ctrl) {
        //example message
        console.log("-- ctrl view value: " + ctrl.$viewValue);
        console.log("-- myMessage: " + scope.myMessage);
        console.log("-- myMessageAsString: " + scope.myMessageAsString);
        console.log("-- myMessageAsExpr: " + scope.myMessageAsExpr());
        console.log("-- myMessageAsExprParam: " + 
                        scope.myMessageAsExprParam({'param1': true}));
        
        //other ways of ‘example message’:
        console.log("-- successfully evaluated value of the attribute myMessage: " + 
                       $parse("myMessage")(scope));
        console.log("-- string value of the attribute myMessage: " + attrs.myMessage);
    }
  };
}]);

By using require we gain the ngModelController that represents the API of the directive for ng-model used on the input element. This controller is then injected as the last parameter in the link function. We did that only to access the $viewValue property of the controller.

As you can see, we are using an isolated scope with four different strategies (actually three, but the last one with a parameter):

  • myMessage: ‘=myMessage’ (data binding of the isolated scope property myMessage with the property of the attribute my-message, in our case a parent scope property message);
  • myMessageAsString: ‘@myMessageAsString’ (passing a String value of attribute my-message-as-string to the isolated scope property myMessageAsString);
  • myMessageAsExpr: ‘&myMessageAsExpr’ (passing a function getMessage() to the isolated scope property myMessageAsExpr, that can be called from the directive);
  • myMessageAsExprParam: ‘&myMessageAsExprParam’ (passing a function getMessage(param1) to the isolated scope variable myMessageAsExprParam, that can be called later but in this case with a parameter);

Remark: we could use a shorter notation for the binding strategies, e.g. myMessage: ‘=’. We can do that if the names of properties are the same on both sides. 

We’re also showing two other ways of outputting our message. An injected $parse service can be used for parsing an isolates scope property myMessage. And we can use directly the attrs parameter of the link function.

Organization of the code

Even though this is a small Angular project, we group our code by functional areas, so that we can observe which objects are related or have dependencies. In our case the functional areas are validation examples.

For each area we also create a manifest file with all dependencies, which looks like as follows:

angular.module("dish4", ['ngRoute']);
angular.module("dish4").config(['$routeProvider', function ($routeProvider) {
    $routeProvider.
        when('/dish4', {
            controller: 'Dish4Controller',
            templateUrl: 'app/dish4/dish4.tpl.html'
        })
}]);
angular.module("dish4").controller("Dish4Controller", ['$scope', Dish4Controller]);
angular.module("dish4").directive("password", PasswordDirective);

In this way we can figure out very quickly what is going on. Implementations are stored in separate files.

Menu 1: Angular build-in validation directives

First of all, we are going to show how to use Angular build-in validation directives ng-required, ng-minlength, ng-pattern. We are only going to show a few segments of the final code. It can be downloaded from GitHub (https://github.com/mpevec/blog/tree/master/js):

<form class="form-horizontal" name="f1" ng-submit="submit()">
    <div class="form-group" ng-class="{'has-error': f1.inputTaxNumber.$dirty && 
                                                    f1.inputTaxNumber.$invalid,
                                       'has-success': f1.inputTaxNumber.$dirty && 
                                                      f1.inputTaxNumber.$valid}">

As you can see, every input has some properties that can help us with validation: $dirty ($pristine), $valid, $invalid. Meanings are self-explanatory.

<input class="form-control" type="text" id="inputTaxNumber" name="inputTaxNumber"
    ng-required="true"
    ng-minlength="8"
    ng-pattern="/^[0-9]+$/"
    ng-model="dish.taxNumber">
<span class="help-block" 
      ng-show="f1.inputTaxNumber.$dirty && 
               f1.inputTaxNumber.$error.required">Required.</span>
<span class="help-block" 
      ng-show="f1.inputTaxNumber.$dirty && 
               f1.inputTaxNumber.$error.minlength">Minimal length is 8.</span>
<span class="help-block" 
      ng-show="f1.inputTaxNumber.$dirty && 
               f1.inputTaxNumber.$error.pattern">Only numbers allowed.</span>

We can very easily validate a required tax number: it must be a valid number with the minimum length of 8 digits. The validation is made as soon as the user enters the data (checking the $dirty flag).

Menu 2: Custom validation with Angular service

Now we are going to implement a bank account validation using the Angular service. It will be called from a custom directive created just for this validation case.

Our service is very simple: it has only one method which checks if the bank account starts with “12345”:

this.validate = function(number) {
    if(number.substring(0, 5) == "12345") {
        return true;
    }
    return false;
}

The input field is similar as before, we just add a custom directive “bank-account”:

<input class="form-control" type="text" id="inputBankAccount" name="inputBankAccount"
    ng-required="true"
    ng-minlength="15"
    ng-maxlength="15"
    ng-pattern="/^[0-9]+$/"
    bank-account
    ng-model="dish.bankAccount">
<span class="help-block"
      ng-show="f1.inputBankAccount.$dirty && 
               f1.inputBankAccount.$error.required">Required.</span>
<span class="help-block" 
      ng-show="f1.inputBankAccount.$dirty && 
               f1.inputBankAccount.$error.minlength">Minimal length is 15.</span>
<span class="help-block" 
      ng-show="f1.inputBankAccount.$dirty && 
               f1.inputBankAccount.$error.maxlength">Maximum length is 15.</span>
<span class="help-block" 
      ng-show="f1.inputBankAccount.$dirty && 
               f1.inputBankAccount.$error.pattern">Only numbers allowed.</span>
<span class="help-block" 
      ng-show="f1.inputBankAccount.$dirty && 
      f1.inputBankAccount.$error.bankAccount">Invalid bank account.</span>

And the directive is implemented like this:

return {
    restrict: 'A',
    require: "ngModel",
    scope: {},
    link: function(scope, element, attrs, ctrl) {
        ctrl.$parsers.push(function(viewValue) {
        if(viewValue) {
           if (BankAccountService.validate(viewValue)) {
              $setValidity('bankAccount', true);
              return viewValue;
           }
           else {
              // it is invalid, return undefined (no model update)
              $setValidity('bankAccount', false);
              return undefined;
           }
        }
    });
}

We require the controller of ng-model directive, which is responsible for validation. We use its API i.e. method $setValidity to set the validation.

We add a new validation function to the end of $parsers array, which contains all the validation methods. In this way, we can be sure that our custom (expensive) validation will be called only at the end of the validation chain and not before.

Menu 3: Username validation after onblur

Let us imagine the next scenario. We have to validate the username at the back-end. Validation can take some time and in between the user should be blocked. And because of the expensive validation, it should only be made after the onblur event.

The solution closely resembles the previous case. There is the input field:

<input class="form-control" type="text" id="inputUsername" name="inputUsername"
    ng-required="true"
    ng-minlength="5"
    ng-model="dish.username"
    username="hasFocus"
    ng-blur="hasFocus=false"
    ng-focus="hasFocus=true">
<span class="help-block" 
      ng-show="f1.inputUsername.$dirty && 
               f1.inputUsername.$error.required">Required.</span>
<span class="help-block" 
      ng-show="f1.inputUsername.$dirty && 
               f1.inputUsername.$error.minlength">Minimal length is 5.</span>
<span class="help-block" 
      ng-show="f1.inputUsername.$dirty && 
               f1.inputUsername.$error.username && 
               !f1.inputUsername.$error.checking">Invalid username.</span>
<span class="help-block" 
      ng-show="f1.inputUsername.$dirty && 
               f1.inputUsername.$error.checking">Checking email...</span>

We use a build-in directives ng-blur, ng-focus, which allows us to set the “hasFocus” scope variable accordingly. In the username directive we use the isolated scope and bind this scope variable to it. Then we can watch it inside the directive and call the validation:

return {
    restrict: 'A',
    require: "ngModel",
    scope: {
        hasFocus: "=username"
    },
    link: function(scope, element, attrs, ctrl) {

        $watch('hasFocus', function(hasFocus) {
            if(angular.isDefined(hasFocus)) {

                // on blur
                if(!hasFocus) {
                    ctrl.$setValidity('checking', false);
                    UsernameService.validate(ctrl.$viewValue)
                        .then(function(resolvedData) {
                            if(resolvedData) {
                                ctrl.$setValidity('checking', true);
                                ctrl.$setValidity('username', true);
                            }
                            else {
                                ctrl.$setValidity('checking', true);
                                ctrl.$setValidity('username', false);
                            }
                        }
                     );
                }
                
                // on focus
                else {
                    ctrl.$setValidity('username', true);
                    ctrl.$setValidity('checking', true);
                }
            }
        });
    }
}

We also operate with the custom “checking” property for two reasons:

– we want to display a text message while the user is waiting for validation to finish;

– we want to block the user until validation is complete.

As you can see, we also remove any validation messages when an onfocus event occurs, just to improve the UI experience.

In our example, we don’t call the back-end, but we simulate it with the help of the $timeout service:

function UsernameService($timeout) {
    this.validate = function(username) {
    return $timeout(function() {
        if(username.substring(0, 5) == "12345") {
            return true;
        }
        return false;
    }, 5000);
}

In this way we can be sure that the validation will last 5 seconds.

Menu 4: Group field validation

In our last example we are going to show how to make a typical group field validation for setting a new password. In this scenario we have two fields that must be validated together, and its values must be equal:

<input class="form-control" type="text" id="inputPassword" name="inputPassword"
    ng-required="true"
    ng-minlength="5"
    ng-model="dish.password">
..
<input class="form-control" type="text" id="inputPasswordR" name="inputPasswordR"
    ng-required="true"
    ng-model="dish.passwordr"
    password="dish.password">

We define the directive as follows:

return {
    restrict: 'A',
    require: "ngModel",
    scope: {
        password: '='
    },
    link: function(scope, element, attrs, ctrl) {
   
        scope.$watch(function() {
            var combined;
            if (scope.password || ctrl.$viewValue) {
                combined = scope.password + '_' + ctrl.$viewValue;
            }
            return combined;
        }, 
        function(value) {
            if (value) {
                if (scope.password !== ctrl.$viewValue) {
                    ctrl.$setValidity("passwordVerify", false);
                } 
                else {
                    ctrl.$setValidity("passwordVerify", true);
                }
            }
        });
    }
}

We have two values that must match. The first one is from the input and is accessible with ctrl.$viewValue. The second one is from the isolated scope property password and holds the value of the first input. With $watch function we can make the validation only when at least one of the values has changed.

Once again the entire code can be found on GitHub: https://github.com/mpevec/blog/tree/master/js/validation.

Advertisements