Working with Validators and Messages in AngularJS

Thursday, October 16, 2014

In a previous post we looked at a custom validation directive to confirm a user’s password.

The bigger picture from the post was not about how to register a user for a website, but how to work with the new validation and validation message features introduced in Angular 1.3, which just fully released this week.

Before we take a step back and look in more detail at what’s new in 1.3, let’s review what stays the same when it comes to form validation.

Same Old Validation

Here are a few points about what has not changed with validation.

1. When you give a <form> a name, Angular will add a property with the same name to the current $scope.

2. All named inputs inside a named <form> will be added to the form’s named property in $scope.

3. Each form and input still have boolean properties to describe if the object is $pristine or $dirty, and $valid or $invalid.

4. Angular still adds CSS classes to each form and input describing the state of the object (ng-dirty, ng-pristine, ng-valid, ng-invalid).

5. There is still an $error property on each form and input. The $error property describes failed validations.

Thanks to the behavior described in the above points, you can have a relatively functional form just using the following markup:

<form name="profileForm"
      ng-submit="profile.submit(profileForm.$valid)"
      novalidate>
      
    <input type="number" name="favoriteNumber" 
           ng-model="profile.number"
           required min="1" />
      
    <input type="submit" value="Save" />
      
</form>

.. and a little bit of CSS ..

.ng-invalid {
  border-color: red;
  border-width: 2px;
}

You can try the above code on Plunker.

The Emperor's  New Validations

emporer Angular 1.3 introduces a number of new features for form validation, including a new approach to writing validation code, a new approach to showing validation error messages, and plenty of small enhancements. like adding ng-submitted as a class to a form after a submit event. To only show validation errors or color code erroneous inputs after the user presses the submit button now only requires a more specific selector in CSS.

form.ng-submitted .ng-invalid {
  border-color: red;
  border-width: 2px;
} 

Try the updated plunk here.

What follows is some more detail about validation messages and the validation API.

ngMessages

One of the messier parts of forms with Angular was the display management of validation errors. The management usually involved ng-if or ng-show directives with ugly expressions like in the following code.

<input type="number" name="favoriteNumber" 
       ng-model="profile.number"
       required min="1" />

<span ng-if="profileForm.favoriteNumber.$error.required">
    Required!
</span>

And this is only a single validation error for a single field. Once you added messages for each validation attribute in each input field, then added class names, there was quite a bit of markup.

ngMessages is a new module for Angular that your application can depend on after adding angular-message.js. Once in place, validation messages look like the following.

<input type="number" name="favoriteNumber" 
       ng-model="profile.number"
       required min="1" /> 
<div ng-messages="profileForm.favoriteNumber.$error" class="errors">
    <span ng-message="required">Required!</span>
    <span ng-message="min">Too small!</span>
</div>

The ngMessages directive will watch the $error property of an input and toggle the first matching ngMessage directive in its messages collection.

Now we’ve gotten most of the uglier expressions out of the markup, but we can also define a template file for messages..

<span ng-message="required">Required!</span>
<span ng-message="min">Too small!</span>
<span ng-message="number">Must be a number!</span>
<span ng-message="odd">Odd numbers only!</span>
<!--  and so on, for all possible validation attributes -->

.. and include the template whenever we need to show error messages for a field.

<div ng-messages="profileForm.favoriteNumber.$error" 
     ng-messages-include="messages.html"
     class="errors">
</div>

Now we’ve removed most of the unsightly and bulky markup from our forms. However, since error messages come from a template they are likely to be generic error messages like the ones in this example, messages like “Too small!”. There is no easy way to poke a field name or attribute value into a message, unfortunately, but there is the ability to override a message.

<div ng-messages="profileForm.favoriteNumber.$error" 
     ng-messages-include="messages.html"
     class="errors">
           
     <!-- overrides the min message from the include -->
     <span ng-message="min">Must be more than 1</span>
</div>

This last bit of code added to the Plunker here.

$validators

Also new in 1.3 is the $validators object on the ngModel controller. Adding a property to this object effectively registers a new validation rule to an input using the name of the property. To get to the $validators property you’ll want to create a custom directive that requires the ngModel controller. The following code adds an “odd” validation rule that I can use to ensure that an input type=”number” will only accept odd numbers.

app.directive("odd", function() {
    return {
        restrict: "A",
        
        require: "ngModel",
        
        link: function(scope, element, attributes, ngModel) {
            ngModel.$validators.odd = function(modelValue) {  
                return modelValue % 2 === 1;
            }
        }
    };
});

Angular automatically invokes the odd function whenever the model value changes (you can also receive the view value as a second parameter)., All the validation function needs to do is return true or false – is the value valid? A return of false will mark the model as invalid and set a property on the associated $error object. To apply the validation rule use the directive on an element using ngModel. 

<input type="number" name="favoriteNumber" 
       ng-model="profile.number"
       required min="1" odd /> 

The associated ngMessage for the attribute will automatically appear when the validation rule fails.

<span ng-message="odd">Odd numbers only!</span>

An updated plunk using the above code lives here.

$asyncValidators

A validation rule added to a model’s $asyncValidator pipeline will typically be a validation rule that needs server-side logic to make a decision. Like everything and anything async in Angular, an async validator will return a promise. If the promise resolves, Angular considers the value valid. If the promise is rejected, the value is invalid.

Here is a prime number validation rule that simulates a slow server-side response using $timeout.

app.directive("prime", function($q, $timeout) {

    var isPrime = function(n) {
      if (n < 2) { 
        return false; 
      }

      for (var i = 2; i <= Math.sqrt(n); i++) {
        if (n % i == 0) {
          return false;
        }
      }
      return true;
    };

    return {
      restrict: "A",
      require: "ngModel",
      link: function(scope, element, attributes, ngModel) {
        ngModel.$asyncValidators.prime = function(modelValue) {
          var defer = $q.defer();
          $timeout(function(){
            if(isPrime(modelValue)) {
              defer.resolve();
            } else {
              defer.reject();
            }
          }, 2000);
          return defer.promise;
        }
      }
   };
});

Using an async validator is no more difficult than using a regular validator, all we need to do is add the directive to the input element.

<input type="number" name="favoriteNumber" 
       ng-model="profile.number"
       required min="1" odd prime /> 
      
<div ng-if="profileForm.favoriteNumber.$pending">
     Calculating....
</div>

Notice you can give some visual clues a pending validation calculation by using the $pending flag provided by Angular.

Try this version of the code in this plunk.

Summary

The summary for this article is intentionally left blank. Like you were going to read it anyway…


Comments
gravatar arik Sunday, October 19, 2014
Thanks, Very nice, clear informative and with great examples.
gravatar Vladyn Monday, October 20, 2014
Really nice explanation - many thanks !
gravatar Jim Thursday, October 23, 2014
Great explanation! Very clear. Keep them coming. Thanks Jim
gravatar Peter Kellner Sunday, October 26, 2014
Hi Scott, I notice you have ng-controller="ProfileController as profile" in your index and in your javascript you have var model = this. I’m having trouble wrapping my head around what is suppose to be the same and what actually is the same. Do you reference in the js model.message but in the html profile.message? Are you doing that to show you can? Because you did one at night and the other the next morning? Or they are unrelated and as usual I’m lost. Hope to see you soon, -Peter
gravatar Peter Kellner Sunday, October 26, 2014
Two things, 1. thanks for reminding me that even numbers are not prime 2. I think from rc4 to release they changed min->minlength and max->maxlength. I wonder if when they go to 2.0 they will do proper casing :)
gravatar sudeep Friday, November 14, 2014
thats awesome ... cool Ex
gravatar danc Wednesday, November 19, 2014
Excellent summary, too
Comments are closed.

My Pluralsight Courses

K.Scott Allen OdeToCode by K. Scott Allen
What JavaScript Developers Should Know About ECMAScript 2015
The Podcast!