left-icon

AngularJS Succinctly®
by Frederik Dietz

Previous
Chapter

of
A
A
A

CHAPTER 7

Using Forms

Using Forms


Every website eventually uses some kind of form for users to enter data. Angular makes it particularly easy to implement client-side form validations to give immediate feedback for an improved user experience.

Implementing a Basic Form

Problem

You wish to create a form to enter user details and capture this information in an Angular.js scope.

Solution

Use the standard form tag and the ng-model directive to implement a basic form:

<body ng-app="MyApp">
  <div ng-controller="User">
    <form ng-submit="submit()" class="form-horizontal" novalidate>
      <label>Firstname</label>
      <input type="text" ng-model="user.firstname"/>
      <label>Lastname</label>
      <input type="text" ng-model="user.lastname"/>
      <label>Age</label>
      <input type="text" ng-model="user.age"/>
      <button class="btn">Submit</button>
    </form>
  </div>
</body>

The novalidate attribute disables the HTML 5 validations, which are client-side validations supported by modern browsers. In our example, we only want the Angular.js validations running to have complete control over the look and feel.

The controller binds the form data to your user model and implements the submit() function:

var app = angular.module("MyApp", []);

app.controller("User", function($scope) {
  $scope.user = {};
  $scope.wasSubmitted = false;

  $scope.submit = function() {
    $scope.wasSubmitted = true;
  };
});

You can find the complete example on GitHub.

Discussion

The initial idea when using forms would be to implement them in the traditional way by serializing the form data and submitting it to the server. Instead, we use ng-model to bind the form to our model—something we have been doing a lot already in previous recipes.

The submit button state is reflected in our wasSubmitted scope variable, but no submission to the server was actually done. The default behavior in Angular.js forms is to prevent the default action since we do not want to reload the whole page. We want to handle the submission in an application-specific way. In fact, there is even more going on in the background. We are going to look into the behavior of the form or ng-form directive in the next recipe.

Validating a Form Model Client-Side

Problem

You wish to validate the form client-side using HTML 5 form attributes.

Solution

Angular.js works in tandem with HTML 5 form attributes. Let us start with the same form but let us add some HTML 5 attributes to make the input required:

<form name="form" ng-submit="submit()">
  <label>Firstname</label>
  <input name="firstname" type="text" ng-model="user.firstname" required/>
  <label>Lastname</label>
  <input type="text" ng-model="user.lastname" required/>
  <label>Age</label>
  <input type="text" ng-model="user.age"/>
  <br>
  <button class="btn">Submit</button>
</form>

It is still the same form, but this time we defined the name attribute on the form and made the input required for the firstname.

Let us add more debug output below the form:

Firstname input valid: {{form.firstname.$valid}}
<br>
Firstname validation error: {{form.firstname.$error}}
<br>
Form valid: {{form.$valid}}
<br>
Form validation error: {{form.$error}}

You can find the complete example on GitHub.

Discussion

When starting with a fresh, empty form, you will notice that Angular adds the CSS class ng-pristine and ng-valid to the form tag and to each input tag. When editing the form, the ng-pristine class will be removed from the changed input field and also from the form tag.  It will be replaced by the ng-dirty class. This is very useful because it allows you to easily add new features to your app depending on these states.

In addition to these two CSS classes, there are two more to look into. The ng-valid class will be added whenever an input is valid; otherwise, the CSS class ng-invalid is added. Note that the form tag also gets either a valid or invalid class depending on the input fields. To demonstrate this, I've added the required HTML 5 attribute. Initially, the firstname and lastname input fields are empty and, therefore, have the ng-invalid CSS class, whereas the age input field has the ng-valid class. Additionally, there's ng-invalid-required class alongside the ng-invalid for even more specificity.

Since we defined the name attribute on the form HTML element, we can now access Angular's form controller via scope variables. In the debug output, we can check the validity and specific error for each named form input and the form itself. Note that this only works on the level of the form's name attributes and not on the model scope. If you output the following expression {{user.firstname.$error}}, it will not work.

Angular's form controller exposes the $valid, $invalid, $error, $pristine, and $dirty variables.

For validation, Angular provides built-in directives including required, pattern, minlength, maxlength, min, and max.

Let us use Angular's form integration to actually show validation errors in the next recipe.

Displaying Form Validation Errors

Problem

You wish to show validation errors to the user by marking the input field red and displaying an error message.

Solution

We can use the ng-show directive to show an error message if a form input is invalid, and CSS classes to change the input element's background color depending on its state.

Let us start with the styling changes:

<style type="text/css">
  input.ng-invalid.ng-dirty {
    background-color: red;
  }
  input.ng-valid.ng-dirty {
    background-color: green;
  }
</style>

And here is a small part of the form with an error message for the input field:

<label>Firstname</label>
<input name="firstname" type="text" ng-model="user.firstname" required/>
<p ng-show="form.firstname.$invalid && form.firstname.$dirty">
  Firstname is required
</p>

You can find the complete example on GitHub.

Discussion

The CSS classes ensure that we initially show the fresh form without any classes. When the user starts typing in some input for the first time, we change it to either green or red. That is a good example of using the ng-dirty and ng-invalid CSS classes.

We use the same logic in the ng-show directive to only show the error message when the user starts typing for the first time.

Displaying Form Validation Errors with the Twitter Bootstrap Framework

Problem

You wish to display form validation errors but the form is styled using Twitter Bootstrap.

Solution

When using the .horizontal-form class, Twitter Bootstrap uses div elements to structure label, input fields, and help messages into groups. The group div has the class control-group and the actual controls are further nested in another div element with the CSS class controls. Twitter Bootstrap shows a nice validation status when adding the CSS class error on the div with the control-group class.

Let us start with the form:

<div ng-controller="User">
  <form name="form" ng-submit="submit()" novalidate>

    <div class="control-group" ng-class="error('firstname')">
      <label class="control-label" for="firstname">Firstname</label>
      <div class="controls">
        <input id="firstname" name="firstname" type="text"
          ng-model="user.firstname" placeholder="Firstname" required/>
        <span class="help-block"
          ng-show="form.firstname.$invalid && form.firstname.$dirty">
          First Name is required
        </span>
      </div>
    </div>

    <div class="control-group" ng-class="error('lastname')">
      <label class="control-label" for="lastname">Lastname</label>
      <div class="controls">
        <input id="lastname" name="lastname" type="text"
          ng-model="user.lastname" placeholder="Lastname" required/>
        <span class="help-block"
          ng-show="form.lastname.$invalid && form.lastname.$dirty">
          Last Name is required
        </span>
      </div>
    </div>

    <div class="control-group">
      <div class="controls">
        <button ng-disabled="form.$invalid" class="btn">Submit</button>
      </div>
    </div>
  </form>
</div>

Note that we use the ng-class directive on the control-group div. So, let's look at the controller implementation of the error function:

app.controller("User", function($scope) {
  // ...
  $scope.error = function(name) {
    var s = $scope.form[name];
    return s.$invalid && s.$dirty ? "error" : "";
  };
});

The error function gets the input name attribute passed as a string and checks for the $invalid and $dirty flags to return either the error class or a blank string.

You can find the complete example on GitHub.

Discussion

Again, we check both the invalid and dirty flags because we only show the error message in case the user has actually changed the form. Note that this ng-class function usage is pretty typical in Angular since expressions do not support ternary checks.

Only Enabling the Submit Button if the Form is Valid

Problem

You wish to disable the Submit button as long as the form contains invalid data.

Solution

Use the $form.invalid state in combination with a ng-disabled directive.

Here is the changed submit button:

<button ng-disabled="form.$invalid" class="btn">Submit</button>

You can find the complete example on GitHub.

Discussion

The Form Controller attributes form.$invalid and friends are very useful to cover all kinds of use cases that focus on the form as a whole instead of individual fields.

Note that you have to assign a name attribute to the form element, otherwise form.$invalid won't be available.

Implementing Custom Validations

Problem

You wish to validate user input by comparing it to a blacklist of words.

Solution

The angular-ui project offers a nice, custom validation directive, which lets you pass in options via expression.

Let us look at the template first, with the usage of the ui-validate directive:

<input name="firstname" type="text"
  ng-model="user.firstname" required
  ui-validate=" { blacklisted: 'notBlacklisted($value)' } "
/>

<p ng-show='form.firstname.$error.blackListed'>
  This firstname is blacklisted.
</p>

And the controller with the notBlackListed implementation:

var app = angular.module("MyApp", ["ui", "ui.directives"]);

app.controller("User", function($scope) {
  $scope.blacklist = ['idiot','loser'];

  $scope.notBlackListed = function(value) {
    return $scope.blacklist.indexOf(value) === -1;
  };
});

You can find the complete example on GitHub.

Discussion

First we need to explicitly list our module dependency to the Angular UI directives module. Make sure you actually download the JavaScript file and load it via script tag.

Our blacklist contains the words we do not want to accept as user input and the notBlackListed function checks to see if the user input matches any of the words defined in the blacklist.

The ui-validate directive is pretty powerful since it lets you define your custom validations easily by just implementing the business logic in a controller function.

If you want to know even more, look at how to implement custom directives yourself in Angular's excellent guide.

Scroll To Top
Disclaimer
DISCLAIMER: Web reader is currently in beta. Please report any issues through our support system. PDF and Kindle format files are also available for download.

Previous

Next



You are one step away from downloading ebooks from the Succinctly® series premier collection!
A confirmation has been sent to your email address. Please check and confirm your email subscription to complete the download.