AngularJS: Form Validation

06 Jun 2013 | By Alex Young | Tags angularjs angularfeeds mvc bower

This week we’re going to look at form validation with AngularJS. Angular has several directives that support form field validation, and they’re based on the HTML5 form validators. You can specify that a field is required, a certain size, a certain type, and should match a given pattern.

URL Validation

Chrome's validation message

This tutorial series is about a feed reader, so it’s lucky that one of the standard HTML5 validators is for checking URLs. It can be used by adding the type="url" attribute to an input. Angular supports this through the input url directive. It takes various options, of which we’re interested in required and ng-model.

The ng-model directive allows the input to be linked to a model, but any Angular expression can be used. The form directive allows forms to be managed with Angular, and bound to controllers.

Just by adding a form and an input with type="url" will result in some basic validation support (in app/views/main.html):

<form name="newFeed">
  URL: <input size="80" name="url" ng-model="newFeed.url" type="url" required>
  <button ng-click="addFeed(newFeed)">Add Feed</button>
</form>

However, this won’t quite work with the controller code that I wrote in the previous parts because addFeed isn’t set up to check validation.

Checking Validation State

In a controller, a bound value can be interrogated for the validation status by checking the $valid property. The previous addFeed, in app/scripts/controllers/main.js, can be changed as follows:

$scope.addFeed = function(feed) {
  if (feed.$valid) {
    // Copy this feed instance and reset the URL in the form
    $scope.feeds.push(feed);
    $scope.newFeed.url = {};
  }
};

This should work, but it does one thing wrong: $scope.newFeed.url can’t be reset by assigning it to an object literal, because newFeed is now decorated with internal properties to support validation. Instead, copy the new object, and reset the values in newFeed:

$scope.addFeed = function(feed) {
  if (feed.$valid) {
    // Copy this feed instance and reset the URL in the form
    var newFeed = angular.copy(feed);
    $scope.feeds.push(newFeed);
    $scope.fetchFeed(newFeed);
    $scope.newFeed.url = '';
  }
};

Fighting with HTML5

We should probably add error messages that are cross-browser compatible. To do that, you can use the ng-show directive:

<form name="newFeed" novalidate>
  URL: <input size="80" name="url" ng-model="newFeed.url" type="url" required>
  <button ng-click="addFeed(newFeed)">Add Feed</button>
  <span class="error" ng-show="newFeed.$error.required">Required!</span>
  <span class="error" ng-show="newFeed.$error.url">Invalid URL format!</span>
</form>

The ngShow directive can conditionally show part of the DOM based on an Angular expression – in this case the validation results are checked. Incidentally, validation results can be found in the $error property fo the model.

Also notice that I added the novalidate attribute to the form; if you don’t do this HTML5 validations will still kick in, which causes confusing behaviour.

Disabling the Button

Another nice touch is to use ng-disabled to disable the button when an invalid URL has been entered. The ngDisabled directive takes an Angular expression, like the previous directives discussed here:

<form name="newFeed" novalidate>
  URL: <input size="80" name="url" ng-model="newFeed.url" type="url" required>
  <button ng-disabled="!newFeed.$valid" ng-click="addFeed(newFeed)">Add Feed</button>
  <span class="error" ng-show="newFeed.$error.required">Required!</span>
  <span class="error" ng-show="newFeed.$error.url">Invalid URL format!</span>
</form>

The difference here is I’ve used ! to negate the expression: !newFeed.$valid. Yes, it’s really that easy!

Conclusion

There’s more to expressions than simple model-based truth tests – you can do pretty much anything short of control flow statements. For more, see Angular Developer Guide, Expressions.

The latest commit for this project was 0dcc996.


blog comments powered by Disqus