Sunday, March 23, 2014

Understanding Form Validation in AngularJS


For my current project I needed to add custom validation to form-input fields in AngularJS. In order to do that, I had to interact with the validation mechanism and to extend it. I started by looking in the AngularJS docs and resources I found on Google. You may gain some basic understanding by reading the resources that are out there, but like with most open-source technologies, to really understand what is going on you need to dive into the source code – in this case the entire Angular source is under angular.js file. Let us see how we can learn about the validation mechanism by understanding the Angular library source code.

Now, assuming your markup tells Angular the following:

 <input type="email" ng-model="user.email" />  

The result will be an input text field that is bound to a certain model property (represented by ng-model). Additionally, if you have bootstrap or any other CSS handling for this scenario the fields will react to the values that are typed inside it, typing anything but a valid email address will result in the field being marked on the screen as invalid.

But what is really happening under the hood? Let us look at the Angular code and see..

First, Angular maps each input tag to its input Directive, which means that when you are working under Angular and writing an “input” tag, your HTML will be compiled and replaced by an Angular directive.

 var inputDirective = ['$browser', '$sniffer', function($browser, $sniffer) {  
  return {  
   restrict: 'E',  
   require: '?ngModel',  
   link: function(scope, element, attr, ctrl) {  
    if (ctrl) {  
     (inputType[lowercase(attr.type)] || inputType.text)(scope, element, attr, ctrl, $sniffer,  
                               $browser);  
    }  
   }  
  };  
 }];  

Note that the directive is expecting your input tag to include an ngModel, the created ngModelController is then passed to the link function. By doing this Angular creates a connection between the input field (the view) and the model. This connection is the core of every validation action.

Angular then uses the type attribute that was used ('email' in our case), to a matching function ('emailInputType' in our case), the code for emailInputType is as follows:


 function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) {  
  textInputType(scope, element, attr, ctrl, $sniffer, $browser);  
  ....  
 }  

In the above code you can see the one of Angualr's internal basic code-reuse pattern. What this code does is referencing the “parent” function textInputType, this function is called from all text input types...from an OO point of view you can almost say that our emailInputType “extends” textInputType.

 function emailInputType(scope, element, attr, ctrl, $sniffer, $browser) {  
  textInputType(scope, element, attr, ctrl, $sniffer, $browser);  
  var emailValidator = function(value) {  
   if (ctrl.$isEmpty(value) || EMAIL_REGEXP.test(value)) {  
    ctrl.$setValidity('email', true);  
    return value;  
   } else {  
    ctrl.$setValidity('email', false);  
    return undefined;  
   }  
  };  
  ctrl.$formatters.push(emailValidator);  
  ctrl.$parsers.push(emailValidator);  
 }  

As you can see, this code uses a simple REGEX validation test, it interacts with the ctrl (the ngModelController that was 'required' in the first snippet). Once a test succeeds or fails, the code updates the ngModelController's $setValidity method. This controller wraps the model used by the input, and the input tells it the model is now valid or invalid.

So what have we learned?

We have learned that:
  1. Angular renders your input HTML tags as directives,
  2. it uses REGEX to match the text inside the directive against the input type pattern (email in our case). 
  3. We have also learned how code reuse or extension like is done for Angular form items.







No comments: