Angular Validation Part 1 – Form Basics

TL;DR:

AngularJS provides a great toolset for form validation. Using the name attribute with a form and its elements publishes a FormController instance to the $scope, giving us fine-grained control over the state of the form via the formName.elementName.$error object.

Check out the fully fleshed-out example on Github.


I’ve been writing in Angular for over a year, and have only recently discovered the Form Controller. I won’t speak to the nastiness of the ‘validation’ factories I wrote in my early Angular days, except to comment that once again, reading the docs would have saved me many lines of code and headaches.

The ‘docs’ in question: Forms, the form (and ng-form) directive, the formController and the ngModelController, from which most of this validation series’ content is gleaned.

In this first part of an Angular Validation series, we will walk through the basics of form validation and explain some debugging techniques, by way of a contrived example that begins with a user-story.

The user-story

As a user, I want to save my name, email, and username on an account page, so that I feel happy and in control of my data.

The only requirement we’ll touch in this post:

  • All three fields are required (i.e., some data entered) before saving any data

The source for the code in this post can be found in this Github repo.

(NOTE: I’m starting this project with the slush-angular-playground generator I built - it provides a happy little gulp-livereload server, plus AngularJS and Bootstrap3 via bower. If you don’t want to use Slush, you can also clone angular-playground directly, or feel free to start from scratch.)

Initial Form

To start, here’s a little form and controller for us to work with.

<div ng-controller="Controller">
  <form name="userForm" ng-submit="save(user)">

    <div class="form-group">
      <input class="form-control" ng-model="user.name"
        placeholder="Name">
    </div>

    <div class="form-group">
      <div class="input-group">
        <span class="input-group-addon">@</span>
        <input class="form-control" ng-model="user.email"
          placeholder="Email">
      </div>
    </div>

    <div class="form-group">
      <input class="form-control" ng-model="user.username"
        placeholder="Username">
    </div>

    <button type="submit" class="btn btn-default pull-right">
      Save
    </button>

  </form>
</div>
angular.module('app', [])
  .controller('Controller', function($scope) {
    $scope.savedUser = null;
    $scope.save = function(user) {
      $scope.savedUser = angular.copy(user);
    };
  });

We’ve got a form (with some Bootstrap styling), and a controller that ‘saves’ our data when the form is submitted. For this exercise, we’ll just be copying our data to a local object that we’ll display elsewhere.

Note the angular form magic - the controller’s save(user) fires via ng-submit, i.e. anytime the form is submitted. This happens whenever submit is clicked, or when ENTER is pressed while focused on one of the input fields.

For now, the form works great. I can type in whatever data I want, hit enter, and it’ll copy the ‘saved’ data as expected and display it to the user. (HTML to display the data is not shown here, see the repo for that code).

Form working, but no validation

However, the form lets me submit even when the fields are empty, which is not what we want. The first step to preventing this: we need to know if the fields are empty or not. In other words, we need the state of the form.

The state of the form

Whenever you name a form in Angular, it publishes the ‘formController’s instance’ to the scope. This instance contains the state of the named elements within that form. We can use this form instance to check that our form as a whole is valid, and to alert the user that parts of their form are either missing or invalid.

Angular provides a few basic validation directives right out of the box, one of which is required. Let’s get comfortable with the form instance and our first piece of validation.

Add required and a name='fieldName' to each input field element, and add a naked {{userForm.$error}} (or add a big debug block as I did) somewhere in your view to see formName.$error in action.

<input name="name" required class="form-control"
  ng-model="user.name" placeholder="Name">
...
<input name="email" required class="form-control"
  ng-model="user.email" placeholder="Email">
...
<input name="username" required class="form-control"
  ng-model="user.username" placeholder="Username">
...
whole form: {{userForm.$error}}
<br>
name: {{userForm.name.$error}}
<br>
email: {{userForm.email.$error}}
<br>
username: {{userForm.username.$error}}

Debug Tip:

Adding a “naked” {{objectOnScope}} into your view is a great way to debug anything in Angular, or just learn about the scope life-cycle.

As you play around with the form at this stage, you’ll see the elementName.$error.required objects move between required:true and required:false. You’ll also see the form’s $error.required object move between an array of seemingly empty objects and required:false.

Displaying form instance changes

We can use userForm.$error (along with the form instance’s named children) to disable/enable submission of the form, and to provide the user with immediate, inline form validation. One way to do this is via ng-show and ng-disabled.

Do you validate?

Let’s hit the inline validation first. Drop some helpful alerts into the form where relevant, using ng-show to show the alert only when necessary.

Here’s my inline-validated name field:

<div class="form-group">
  <input name="name" class="form-control"
    ng-model="user.name" placeholder="Name" required>
  <div class="alert alert-danger"
    ng-show="userForm.name.$error.required">
    Please enter your name!
  </div>
</div>

This works great! Except it’s a little bit eager. We really don’t mind that the field is empty until a user has touched it. Angular provides a great solution for this: the $dirty state.

$dirty works similarly to $error – drop it ‘naked’ into your view if you want to experiment with it.

To hide the required error until the form has been ‘dirtied’, update your ng-show conditional:

ng-show="userForm.name.$error.required && userForm.name.$dirty"

Your user can now see a beautiful form on landing, and add their name. If they should remove their name from the input field, our happy little reminder will kindly remind them it is required.

Name required, sir or madam!

The other fields in your form can get the same treatment, if you wish.

This functions pretty well, except for one problem: the invalid form can still be submitted!

(NOTE: This isn’t 100% true - some browsers prevent required fields from being submitted if empty, and provide some inline validation of their own to help you out. This isn’t universal, and is also a fallback. What’s happening: the form is attempting a submit, but that submit event is being blocked by the HTML itself. I’d rather prevent the form from being submitted in the first place - a much cleaner, more universal solution.)

Help me help you

We’ve seen how formName.$error allowed us to specify which error existed on the form element. For checking on general form or element validity, Angular provides two more flags: formName.$valid and formName.$invalid.

In order to disable our form, we’re goign to move the save(user) function from the ng-submit on the form element to an ng-click on our <button type="submit">. This change does not change the submission behavior of the form (see the form docs for form submission behavior).

An isolated ng-click allows us to add another helpful directive, ng-disabled, to the same <button> element. ng-disabled takes a conditional, and adds/removes the HTML attribute disabled, providing just the behavior we want.

<!-- remove ng-submit from form element -->
<form name="userForm">
...
<!-- add ng-click and ng-disabled, use userForm.$invalid -->
<button type="submit" class="btn btn-default pull-right"
  ng-click="save(user)" ng-disabled="userForm.$invalid">
  Save
</button>

With these directives, the form cannot be submitted when it is invalid, and the button’s faded presentation reflects that.

Disabled form

That’s validating

Thus, the basics of Angular validation. To summarize:

  • Name your forms and elements to publish their state to the scope
  • Use formName.elementName.$error.errorName to specify the issue with the form
  • Use formName.elementName.$dirty to check if the element has been touched
  • Use ng-disabled and formName.$invalid on your type="submit" element to prevent an invalid form submission.

There’s plenty left to do here. Some ideas for next steps:

  • Add validation for the email address - at this point, that should be child’s play
  • Clean up some of that styling!
  • Toy with the other Angular built-in validators: pattern, minlength, maxlength, min, max

In Part 2 of this Angular Validation series, we’ll build a custom directive to perform our own validation, throwing our own $error, and informing the user exactly what it is we’re looking for.

Anything look weird? Is this style of validation awesome, or bizarre? Looking for more detail on anything mentioned here? Drop any questions, comments, or concerns below!

comments powered by Disqus