Aug 4, 2015

Form validation with AngularJS



Client-side form validations are one of the coolest features inside of AngularJS. AngularJS form validation enables you to write a modern HTML5 form that is interactive and responsive from the start.

There are many form validation directives available in AngularJS. We’ll talk about a few of the most popular ones here and then we’ll get into how to build your own validations.

<form name="form">
  <label name="email">Your email</label>
  <input type="email" name="email" ng-model="email" placeholder="Email Address" />
</form>

AngularJS makes it pretty easy for us to handle client-side form validations without adding a lot of extra effort. Although we can’t depend on client-side validations keeping our web application secure, they provide instant feedback of the state of the form.

To use form validations, we first must ensure that the form has a name associated with it, like in the above example. Got it? Great!

All input fields can validate against some basic validations, like minimum length, maximum length, etc. These are all available on the new HTML5 attributes of a form.

It is usually a great idea to use the novalidate flag on the form element. This will prevent the browser from submitting the form.

Let’s look at all the validation options we have that we can place on an input field:
Required

To validate that a form input has been filled out, simply add the html5 tag: required to the input field:

<input type="text" required />

Minimum length

To validate that a form input input is at least a {number} of characters, add the AngularJS directive ng-minlength="{number}" to the input field:

<input type="text" ng-minlength=5 />

Maximum length

To validate that a form input is equal to or less than a number of characters, add the AngularJS directive ng-maxlength="{number}":

<input type="text" ng-maxlength=20 />

Matches a pattern

To ensure that an input matches a regex pattern, use the AngularJS directive: ng-pattern="/PATTERN/":

<input type="text" ng-pattern="[a-zA-Z]" />

Email

To validate an email address in an input field, simply set the input type to email, like so:

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

Number

To validate an input field has a number, set the input type to number:

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

Url
To validate that an input represents a url, set the input type to url:

<input type="url" name="homepage" ng-model="user.facebook_url" />

Custom validations

AngularJS makes it very easy to add your own validations as well by using directives. For instance, let’s say that we want to validate that our username is available in the database. To do this, we’ll implement a directive that fires an ajax request whenever the form changes:

var app = angular.module('validationExample', []);

app.directive('ensureUnique', ['$http', function($http) {
  return {
    require: 'ngModel',
    link: function(scope, ele, attrs, c) {
      scope.$watch(attrs.ngModel, function() {
        $http({
          method: 'POST',
          url: '/api/check/' + attrs.ensureUnique,
          data: {'field': attrs.ensureUnique}
        }).success(function(data, status, headers, cfg) {
          c.$setValidity('unique', data.isUnique);
        }).error(function(data, status, headers, cfg) {
          c.$setValidity('unique', false);
        });
      });
    }
  }
}]);

Control variables in forms

AngularJS makes properties available on the containing $scope object available to us as a result of setting a form inside the DOM. These enable us to react to the form in realtime (just like everything else in AngularJS). The properties that are available to us are:

Note that these properties are made available to us in the format:

formName.inputFieldName.property

Unmodified form

A boolean property that tells us if the user has modified the form. This is true if the user hasn’t touched the form, and false if they have:

formName.inputFieldName.$pristine;

Modified form

A boolean property if and only if the user has actually modified the form. This is set regardless of validations on the form:

formName.inputFieldName.$dirty

Valid form

A boolean property that tells us that the form is valid or not. If the form is currently valid, then this will be true:

formName.inputFieldName.$valid

Invalid form

A boolean property that tells us that the form is invalid. If the form is currently invalid, then this will be true:

formName.inputFieldName.$invalid

The last two properties are particularly useful for showing or hiding DOM elements. They are also very useful when setting a class on a particular form.
Errors

Another useful property that AngularJS makes available to us is the $error object. This object contains all of the validations on a particular form and if they are valid or invalid. To get access to this property, use the following syntax:

formName.inputfieldName.$error

If a validation fails then this property will be true, while if it is false, then the value has passed the input field.
A little style never hurts

When AngularJS is handling a form, it adds specific classes to the form based upon their state. These classes are named similar to the properties that we can check as well.

These classes are:

.ng-pristine {}
.ng-dirty {}
.ng-valid {}
.ng-invalid {}

The correspond to their counterpart on the particular input field.

When a field is invalid, the .ng-invalid class will be applied to the field. This particular site sets the css class as:

input.ng-invalid {
  border: 1px solid red;
}
input.ng-valid {
  border: 1px solid green;
}

Putting it all together

Let’s build a signup form. This signup form will include the person’s name, their email, and a desired username.

Let’s start by looking at what the form will look like when it’s done. Go ahead, play with it, start typing in the input fields, see how the form responds and reacts. Notice that the submit button becomes disabled when the form goes invalid:
Signup form
Your name
Your email
Username

Let’s start with defining the form:

<form name="signup_form" novalidate ng-submit="signupForm()">
  <fieldset>
    <legend>Signup</legend>

    <button type="submit" class="button radius">Submit</button>
  </fieldset>
</form>

This form’s name is signup_form and we are going to call signupForm() when the form is submitted.

Now, let’s add the name of the user:

<div class="row">
  <div class="large-12 columns">
    <label>Your name</label>
    <input type="text"
        placeholder="Name"
        name="name"
        ng-model="signup.name"
        ng-minlength=3
        ng-maxlength=20 required />
  </div>
</div>

Try it
Your name

Breaking this down. First thing to note is that I use Foundation for my css layouts, so you’ll see that syntax in my forms. We added a form that has an input called name that is bound (by ng-model) to the object signup.name on the $scope object.

We also setup a few validations. These validations say we have to have a minlength of our name of 3 or more characters. We also impose a maximum limit of characters of 20 characters (this will be invalid at 21 characters and higher). Lastly, we require that the name be filled out for the form to be valid.

Let’s use the properties to show and/or hide a list of errors if the form is invalid. We’ll also use the $dirty attribute to make sure the errors don’t show up if the user hasn’t touched the field:

<div class="row">
  <div class="large-12 columns">
    <label>Your name</label>
    <input type="text"
        placeholder="Name"
        name="name"
        ng-model="signup.name"
        ng-minlength=3
        ng-maxlength=20 required />
   <div class="error"
        ng-show="signup_form.name.$dirty && signup_form.name.$invalid">
    <small class="error"
        ng-show="signup_form.name.$error.required">
        Your name is required.
    </small>
    <small class="error"
            ng-show="signup_form.name.$error.minlength">
            Your name is required to be at least 3 characters
    </small>
    <small class="error"
            ng-show="signup_form.name.$error.maxlength">
            Your name cannot be longer than 20 characters
    </small>
  </div>
  </div>
</div>

Try it
Your name

Breaking this down, we’re only going to show our errors if the form is invalid and changed, just as before. This time, we’ll look through each of the valiations and only show a particular DOM element if the particular validation property is invalid.

Let’s look at the next set of validations, the email validation:

<div class="row">         
  <div class="large-12 columns">
    <label>Your email</label>
    <input type="email"
      placeholder="Email"
      name="email"
      ng-model="signup.email"
      ng-minlength=3 ng-maxlength=20 required />
    <div class="error-container"
         ng-show="signup_form.email.$dirty && signup_form.email.$invalid">
      <small class="error"
             ng-show="signup_form.email.$error.required">
             Your email is required.
      </small>
      <small class="error"
             ng-show="signup_form.email.$error.minlength">
              Your email is required to be at least 3 characters
      </small>
      <small class="error"
             ng-show="signup_form.email.$error.email">
             That is not a valid email. Please input a valid email.
      </small>
      <small class="error"
             ng-show="signup_form.email.$error.maxlength">
              Your email cannot be longer than 20 characters
      </small>
    </div>
  </div>
</div>

Try it
Your email

This time (with the entire form included), we’re looking at the email field. Note that we set the type of the input field to email and added a validation error on $error.email. This is based off the AngularJS email validation (and the HTML5 attribute).

Finally, let’s look at our last input field, the username:

<div class="large-12 columns">
  <label>Username</label>
    <input  type="text"
            placeholder="Desired username"
            name="username"
            ng-model="signup.username"
            ng-minlength=3
            ng-maxlength=20
            ensure-unique="username" required />
  <div class="error-container" ng-show="signup_form.username.$dirty && signup_form.username.$invalid">
    <small class="error" ng-show="signup_form.username.$error.required">Please input a username</small>
    <small class="error" ng-show="signup_form.username.$error.minlength">Your username is required to be at least 3 characters</small>
    <small class="error" ng-show="signup_form.username.$error.maxlength">Your username cannot be longer than 20 characters</small>
    <small class="error" ng-show="signup_form.username.$error.unique">That username is taken, please try another</small>
  </div>
</div>

Try it
Username

In our last field, we’re using all the same previous validations, with the exception that we’ve added our custom validation. This custom validation is defined using an AngularJS directive:

app.directive('ensureUnique', ['$http', function($http) {
  return {
    require: 'ngModel',
    link: function(scope, ele, attrs, c) {
      scope.$watch(attrs.ngModel, function() {
        $http({
          method: 'POST',
          url: '/api/check/' + attrs.ensureUnique,
          data: {'field': attrs.ensureUnique}
        }).success(function(data, status, headers, cfg) {
          c.$setValidity('unique', data.isUnique);
        }).error(function(data, status, headers, cfg) {
          c.$setValidity('unique', false);
        });
      });
    }
  }
}]);

When the form input is valid, this will make a POST request check to the server at /api/check/username to check if the username is available. Now, obviously since we’re only talking about front-end code here, we don’t have a backend to test this on, although it could easily be written.

Update: As per a discussion in the comments, I’ve added an update using the $timeout service. To check out that full source, check it out here.

Lastly, putting our button together, we can use the angular directive ng-disabled to disable and reenable the button when the form is valid:

<button type="submit" ng-disabled="signup_form.$invalid" class="button radius">Submit</button>

As we said above, the form itself will have a $invalid and valid attributes given to us for free.

Update 2: Although live validation is great, it can be abrasive to the user when they see errors pop-up while they are typing, long before they have put in a valid value. You can be nicer to your users if you show the validations either only after they have submitted the form or after they have moved off of the input. Let’s look at both ways to do that.
Show validations after submit

To show validations only after the user has attempted to submit the form, you can capture a ‘submitted’ value on the scope and check for that scope to show the error.

For instance, let’s take a look at the first example and only show the errors when the form has been submitted. On the ng-show directive on for the form input, we can add a check to see if the form has been submitted (which we will implement shortly):

<form name="signup_form" novalidate ng-submit="signupForm()">
  <fieldset>
    <legend>Signup</legend>
    <div class="row">
      <div class="large-12 columns">
        <label>Your name</label>
        <input type="text"
            placeholder="Name"
            name="name"
            ng-model="signup.name"
            ng-minlength=3
            ng-maxlength=20 required />
       <div class="error-container"
            ng-show="signup_form.name.$dirty && signup_form.name.$invalid && signup_form.submitted">
        <small class="error"
            ng-show="signup_form.name.$error.required">
            Your name is required.
        </small>
        <small class="error"
                ng-show="signup_form.name.$error.minlength">
                Your name is required to be at least 3 characters
        </small>
        <small class="error"
                ng-show="signup_form.name.$error.maxlength">
                Your name cannot be longer than 20 characters
        </small>
      </div>
      </div>
    </div>
    <button type="submit" class="button radius">Submit</button>
  </fieldset>
</form>

Now, the error div will only show up if the signup_form.submitted variable has been set to true. We can implement this in the signupForm action, like so:

app.controller('signupController', ['$scope', function($scope) {
  $scope.submitted = false;
  $scope.signupForm = function() {
    if ($scope.signup_form.$valid) {
      // Submit as normal
    } else {
      $scope.signup_form.submitted = true;
    }
  }
}]);

Now, when your users try to submit the form while there is an invalid element, you can catch it and show them the appropriate errors.
Show validations only after blur

If you want to retain the real-time nature of the error input, you can show your users the errors after they have blurred off of the input form. To do this, we like to add a small directive that will attach a new variable to the form.

The directive we like to use is the ngFocus directive and it looks like:

app.directive('ngFocus', [function() {
  var FOCUS_CLASS = "ng-focused";
  return {
    restrict: 'A',
    require: 'ngModel',
    link: function(scope, element, attrs, ctrl) {
      ctrl.$focused = false;
      element.bind('focus', function(evt) {
        element.addClass(FOCUS_CLASS);
        scope.$apply(function() {ctrl.$focused = true;});
      }).bind('blur', function(evt) {
        element.removeClass(FOCUS_CLASS);
        scope.$apply(function() {ctrl.$focused = false;});
      });
    }
  }
}]);

To implement the ngFocus directive, we can simply attach this directive to the input element, like so:

<input ng-class="{error: signup_form.name.$dirty && signup_form.name.$invalid}" type="text" placeholder="Name" name="name" ng-model="signup.name" ng-minlength=3 ng-maxlength=20 required ng-focus />

The ngFocus directive simply attaches an action to the blur and focus events on the form input and adds a class (ng-focused) and sets the form input field $focused as true. Then you can show your individual error messages depending if the form is focused or not. For instance:

<div class="error-container" ng-show="signup_form.name.$dirty && signup_form.name.$invalid && !signup_form.name.$focused">

I hope this post shows you how cool AngularJS form validation can be. Stay tuned for the next post and sign-up for the newsletter for weekly, up-to-date posts about the latest and greatest, curated angularjs news.

No comments:

Post a Comment