Chapter 4. Forms, Inputs, and Services

In the previous chapters, we first covered the most basic AngularJS directives and dealt with creating controllers and getting our data from the controllers into the UI. We then looked at how to write tests for the same, using Karma and Jasmine. In this chapter, we will build on the work from Chapter 2 and work on getting the user’s data out of forms in the UI into our controller so that we can then send it to the server, validate it, or do whatever else we might need to.

We will then get into using AngularJS services, and see how we can leverage some of the common existing services as well as create our own. We will also briefly cover when and why you should create AngularJS services.

Working with ng-model

In the previous chapter, we saw the ng-bind directive, or its equivalent double-curly {{ }} notation, which allowed us to take the data from our controllers and display it in the UI. That gives us our one-way data-binding, which is powerful in its own regard. But most applications we develop also have user interaction, and parts where the user has to feed in data. From registration forms to profile information, forms are a staple of web applications, and AngularJS provides the ng-model directive for us to deal with inputs and two-way data-binding:

<!-- File: chapter4/simple-ng-model.html -->
<html ng-app="notesApp">
<head><title>Notes App</title></head>
<body ng-controller="MainCtrl as ctrl">

  <input type="text" ng-model="ctrl.username"/>
  You typed {{ctrl.username}}

<script
  src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.11/angular.js">
</script>
<script type="text/javascript">
  angular.module('notesApp', [])
    .controller('MainCtrl', [function() {
      this.username = 'nothing';
    }]);
</script>
</body>
</html>

In this example, we define a controller with a variable exposed on its instance called username. Now, we get its value out into the HTML using the ng-controller and the double-curly syntax for one-way data-binding. What we have introduced in addition is an input element. It is a plain text box, but on it we have attached the ng-model directive. We pointed the value for the ng-model at the same username variable on the MainCtrl. This accomplishes the following things:

  • When the HTML is instantiated and the controller is attached, it gets the current value (in this case, nothing as a string) and displays it in our UI.
  • When the user types, updates, or changes the value in the input box, it updates the model in our controller.
  • When the value of the variable changes in the controller (whether because it came from the server, or due to some internal state change), the input field gets the value updated automatically.

The beauty of this is twofold:

  • If we need to update the form element in the UI, all we need to do is update the value in the controller. No need to go looking for input fields by IDs or CSS class selectors; just update the model.
  • If we need to get the latest value that the user entered into the form or input to validate or send to the server, we just need to grab it from our controller. It will have the latest value in it.

Now let’s add some complexity, and actually deal with forms. Let’s see if we can bring this concept together with an example:

<!-- File: chapter4/simple-ng-model-2.html -->
<html ng-app="notesApp">
<head><title>Notes App</title></head>
<body ng-controller="MainCtrl as ctrl">

  <input type="text" ng-model="ctrl.username">
  <input type="password" ng-model="ctrl.password">
  <button ng-click="ctrl.change()">Change Values</button>
  <button ng-click="ctrl.submit()">Submit</button>

<script
  src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.11/angular.js">
</script>
<script type="text/javascript">
  angular.module('notesApp', [])
    .controller('MainCtrl', [function() {
      var self = this;
      self.change = function() {
        self.username = 'changed';
        self.password = 'password';
      };
      self.submit = function() {
        console.log('User clicked submit with ',
            self.username, self.password);
      };
    }]);
</script>
</body>
</html>

We introduced one more input field, which is bound to a field called password on the controller’s instance. And we added two buttons:

  • The first button, Change Values, is to simulate the server sending some data that needs to be updated in the UI. All it does is reassign the values to the username and password fields in the controller with the latest values.
  • The second button, Submit, simulates submitting the form to the server. All it does for now is log the value to the console.

The most important thing in both of these is that the controller never reached out into the UI. There was no jQuery selector, no findElementById, or anything like that. When we need to update the UI, we just update the model fields in the controller. When we need to get the latest and greatest value, we just grab it from the controller. Again, this is the AngularJS way.

Next let us see how to leverage this and work with forms in AngularJS.

Working with Forms

When we work with forms in AngularJS, we heavily leverage the ng-model directive to get our data into and out of the form. In addition to the data-binding, it is also recommended to structure your model and bindings in such a way to reduce your own effort, as well as the lines of code you write. Let’s take a look at an example:

<!-- File: chapter4/simple-form.html -->
<html ng-app="notesApp">
<head><title>Notes App</title></head>
<body ng-controller="MainCtrl as ctrl">

  <form ng-submit="ctrl.submit()">
    <input type="text" ng-model="ctrl.user.username">
    <input type="password" ng-model="ctrl.user.password">
    <input type="submit" value="Submit">
  </form>

<script
  src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.11/angular.js">
</script>
<script type="text/javascript">
  angular.module('notesApp', [])
    .controller('MainCtrl', [function() {
      var self = this;
      self.submit = function() {
        console.log('User clicked submit with ', self.user);
      };
    }]);
</script>
</body>
</html>

We are still using the same two input fields as last time, but we made a few changes:

  • We wrapped our text fields and button inside a form. And instead of an ng-click on the button, we added an ng-submit directive on the form itself. The ng-submit directive has a few advantages over having an ng-click on a button when it comes to forms. A form submit event can be triggered in multiple ways: clicking the Submit button, or hitting Enter on a text field. The ng-submit gets triggered on all those events, whereas the ng-click will only be triggered when the user clicks the button.
  • Instead of binding to ctrl.username and ctrl.password, we bind to ctrl.user.username and ctrl.user.password. Notice that we did not declare a user object in the controller (that is, self.user = {}). When you use ng-model, AngularJS automatically creates the objects and keys necessary in the chain to instantiate a data-binding connection. In this case, until the user types something into the username or password field, there is no user object. The first letter typed into either the username or password field causes the user object to be created, and the value to be assigned to the correct field in it.

Leverage Data-Binding and Models

When designing your forms and deciding which fields to bind the ng-model to, you should always consider what format you need the data in. Let’s take the following example to demonstrate:

<!-- File: chapter4/two-forms-databinding.html -->
<html ng-app="notesApp">
<head><title>Notes App</title></head>
<body ng-controller="MainCtrl as ctrl">

  <form ng-submit="ctrl.submit1()">
    <input type="text" ng-model="ctrl.username">
    <input type="password" ng-model="ctrl.password">
    <input type="submit" value="Submit">
  </form>

  <form ng-submit="ctrl.submit2()">
    <input type="text" ng-model="ctrl.user.username">
    <input type="password" ng-model="ctrl.user.password">
    <input type="submit" value="Submit">
  </form>

<script
  src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.11/angular.js">
</script>
<script type="text/javascript">
  angular.module('notesApp', [])
    .controller('MainCtrl', [function() {
      var self = this;
      self.submit1 = function() {
        // Create user object to send to the server
        var user = {username: self.username, password: self.password};
        console.log('First form submit with ', user);
      };
      self.submit2 = function() {
        console.log('Second form submit with ', self.user);
      };
    }]);
</script>
</body>
</html>

There are two forms in this example, both with the same fields. The first form is bound to a username and password directly on the controller, while the second form is bound to a username and password key on a user object in the controller. Both of them trigger an ng-submit function on submission of a function. Now in the case of the first form, we have to take those fields from the controller and put them into an object, or something similar, before we can send it to the server. In the second case, we can directly take the user object from the controller and pass it around.

The second flow makes more sense, because we are directly modeling how we want to represent the form as an object in the controller. This removes any additional work we might have to do when we work with the values of the form.

Form Validation and States

We have seen how to create forms, and enable (and leverage) data-binding to get our data in and out of the UI. Now let’s proceed to see how else AngularJS can benefit us when working with forms, and especially with validation and various states of the forms and inputs:

<!-- File: chapter4/form-validation.html -->
<html ng-app="notesApp">
<head><title>Notes App</title></head>
<body ng-controller="MainCtrl as ctrl">

  <form ng-submit="ctrl.submit()" name="myForm">
    <input type="text"
           ng-model="ctrl.user.username"
           required
           ng-minlength="4">
    <input type="password"
           ng-model="ctrl.user.password"
           required>
    <input type="submit"
           value="Submit"
           ng-disabled="myForm.$invalid">
  </form>

<script
  src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.11/angular.js">
</script>
<script type="text/javascript">
  angular.module('notesApp', [])
    .controller('MainCtrl', [function() {
      var self = this;
      self.submit = function() {
        console.log('User clicked submit with ', self.user);
      };
    }]);
</script>
</body>
</html>

In this example, we reworked our old example to add some validation. In particular, we want to disable the Submit button if the user has not filled out all the required fields. How do we accomplish this?

  1. We give the form a name, which we can refer to later. In this case, it is myForm.
  2. We leverage HTML5 validation tags and add the required attribute on each input field.
  3. We add a validator, ng-minlength, which enforces that the minimum length of the value in the input field for the username is four characters.
  4. On the Submit button, we add an ng-disabled directive. This disables the element if the condition is true.
  5. For the disable condition, we leverage the form, which exposes a controller with the current state of the form. In this case, we tell the button to disable itself if the form with the name myForm is $invalid.

Warning

Do note that in case you have added a validator to any input field, the ng-model value is not set unless and until the form field is valid. That is, in the example that precedes this, unless the minimum length is satisfied, the value in the input field will not be set in the ng-model variable (ctrl.user.username). It will remain empty until the minimum length condition is met.

When you use forms (and give them names), AngularJS creates a FormController that holds the current state of the form as well as some helper methods. You can access the FormController for a form using the form’s name, as we did in the preceding example using myForm. Things that are exposed as the state and kept up to date with data-binding are shown in Table 4-1.

Table 4-1. Form states in AngularJS
Form stateDescription

$invalid

AngularJS sets this state when any of the validations (required, ng-minlength, and others) mark any of the fields within the form as invalid.

$valid

The inverse of the previous state, which states that all the validations in the form are currently evaluating to correct.

$pristine

All forms in AngularJS start with this state. This allows you to figure out if a user has started typing in and modifying any of the form elements. Possible usage: disabling the reset button if a form is pristine.

$dirty

The inverse of $pristine, which states that the user made some changes (he can revert it, but the $dirty bit is set).

$error

This field on the form houses all the individual fields and the errors on each form element. We will talk more about this in the following section.

Each of the states mentioned in the table (except $error) are Booleans and can be used to conditionally hide, show, disable, or enable HTML elements in the UI. As the user types or modifies the form, the values are updated as long as you are leveraging ng-model and the form name.

Error Handling with Forms

We looked at the types of validation you can do at a form level, but what about individual fields? In our previous example, we ensured that both input fields were required fields, and that the minimum length on the username was four. What else can we do? Table 4-2 contains some built-in validations that AngularJS offers.

Table 4-2. Built-in AngularJS validators
ValidatorDescription

required

As previously discussed, this ensures that the field is required, and the field is marked invalid until it is filled out.

ng-required

Unlike required, which marks a field as always required, the ng-required directive allows us to conditionally mark an input field as required based on a Boolean condition in the controller.

ng-minlength

We can set the minimum length of the value in the input field with this directive.

ng-maxlength

We can set the maximum length of the value in the input field with this directive.

ng-pattern

The validity of an input field can be checked against the regular expression pattern specified as part of this directive.

type="email"

Text input with built-in email validation.

type="number"

Text input with number validation. Can also have additional attributes for min and max values of the number itself.

type="date"

If the browser supports it, shows an HTML datepicker. Otherwise, defaults to a text input. The ng-model that this binds to will be a date object. This expects the date to be in yyyy-mm-dd format (e.g., 2009-10-24). This is introduced in AngularJS version 1.3.0 onwards.

type="url"

Text input with URL validation.

In addition to this, we can write our own validators, which we cover in Chapter 13.

Displaying Error Messages

What can we do with all these validators? We can of course check the validity of the form, and disable the Save or Update button accordingly. But we also want to tell the user what went wrong and how to fix it. AngularJS offers two things to solve this problem:

  • A model that reflects what exactly is wrong in the form, which we can use to display nicer error messages
  • CSS classes automatically added and removed from each of these fields allow us to highlight problems in the form

Let’s first take a look at how to display specific error messages based on the problem with the following example:

<!-- File: chapter4/form-error-messages.html -->
<html ng-app="notesApp">
<head><title>Notes App</title></head>
<body ng-controller="MainCtrl as ctrl">

  <form ng-submit="ctrl.submit()" name="myForm">
    <input type="text"
           name="uname"
           ng-model="ctrl.user.username"
           required
           ng-minlength="4">
    <span ng-show="myForm.uname.$error.required">
      This is a required field
    </span>
    <span ng-show="myForm.uname.$error.minlength">
      Minimum length required is 4
    </span>
    <span ng-show="myForm.uname.$invalid">
      This field is invalid
    </span>
    <input type="password"
           name="pwd"
           ng-model="ctrl.user.password"
           required>
    <span ng-show="myForm.pwd.$error.required">
      This is a required field
    </span>
    <input type="submit"
           value="Submit"
           ng-disabled="myForm.$invalid">
  </form>

<script
  src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.11/angular.js">
</script>
<script type="text/javascript">
  angular.module('notesApp', [])
    .controller('MainCtrl', [function () {
      var self = this;
      self.submit = function () {
        console.log('User clicked submit with ', self.user);
      };
    }]);
</script>
</body>
</html>

Nothing in the controller has changed in this example. Instead, we can just focus on the form HTML. Let’s see what changed with the form:

  1. First, we added the name attribute to both the input fields where we needed validation: uname for the username box, and pwd for the password text field.
  2. Then we leverage AngularJS’s form bindings to be able to pick out the errors for each individual field. When we add a name to any input, it creates a model on the form for that particular input, with the error state.
  3. So for the username field, we can access it if the field was not entered by accessing myForm.uname.$error.required. Similarly, for ng-minlength, the field would be myForm.uname.$error.minlength. For the password, we look at myForm.pwd.$error.required to see if the field was filled out or not.
  4. We also accessed the state of the input, similar to the form, by accessing myForm.uname.$invalid. All the other form states ($valid, $pristine, $dirty) we saw earlier are also available similarly on myForm.uname.

With this, we now have an error message that shows only when a certain type of error is triggered. Each of the validators we saw in Table 4-2 exposes a key on the $error object, so that we can pick it up and display the error message for that particular error to the user. Need to show the user that a field is required? Then when the user starts typing, show the minimum length, and then finally show a message when he exceeds the maximum length. All these kinds of conditional messages can be shown with the AngularJS validators.

ngMessages

In the previous section, we saw how to display conditional error messages for forms. But there are cases when we have more complex conditions where multiple error conditions might be met. We might also want to use common error messages across all our pages and form fields. To help us with these conditions, there in an optional module in AngularJS called ngMessages. This is not part of the core AngularJS file (the angular.js file), but an extra JavaScript file you have to include to get this functionality. What the ngMessages module provides us with is:

  • The ability to cleanly define the messages for each error, instead of relying on complex if and show conditions
  • The ability to sequence the error messages by priority
  • The ability to inherit and extend error messages across the application

To be able to use ngMessages in your project, you need to do the following:

  1. Include the angular-messages.js (or its minified version) in your main index.html
  2. Add the ngMessages module as a dependency to the main app module
  3. Use the ng-messages and ng-message directives
  4. Optionally define templates for your common error messages

Let’s take a full fledged example of these in action to see how it would work:

<!-- File: chapter4/ng-messages.html -->
<html ng-app="notesApp">
<head><title>Notes App</title></head>
<body ng-controller="MainCtrl as ctrl">

  <form ng-submit="ctrl.submit1()" name="simpleForm">
    <input type="email"
           name="uname"
           ng-model="ctrl.user1.username"
           required
           ng-minlength="6">
    <div ng-messages="simpleForm.uname.$error"
         ng-messages-include="error-messages"></div>
    <input type="password"
           name="pwd"
           ng-model="ctrl.user1.password"
           required>
    <div ng-messages="simpleForm.pwd.$error"
         ng-messages-include="error-messages"></div>
    <input type="submit"
           value="Submit"
           ng-disabled="simpleForm.$invalid">
  </form>

  <form ng-submit="ctrl.submit2()" name="overriddenForm">
    <input type="email"
           name="uname"
           ng-model="ctrl.user2.username"
           required
           ng-minlength="6">
    <div ng-messages="overriddenForm.uname.$error"
         ng-messages-include="error-messages">
      <div ng-message="required">Please enter a username</div>
    </div>
    <input type="password"
           name="pwd"
           ng-model="ctrl.user2.password"
           required>
    <div ng-messages="overriddenForm.pwd.$error">
      <div ng-message="required">Please enter a password</div>
    </div>
    <input type="submit"
           value="Submit"
           ng-disabled="overriddenForm.$invalid">
  </form>

<script
  src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.11/angular.js">
</script>
<script src=
  "https://ajax.googleapis.com/ajax/libs/angularjs/1.3.11/angular-messages.js">
</script>
<script type="text/ng-template" id="error-messages">
  <div ng-message="required">This field is mandatory</div>
  <div ng-message="minlength">Minimum length condition not met</div>
  <div ng-message="email">Please enter a valid email address</div>
</script>
<script type="text/javascript">
  angular.module('notesApp', ['ngMessages'])
    .controller('MainCtrl', [function() {
      var self = this;
      self.submit1 = function() {
        console.log('User clicked submit with ', self.user1);
      };
      self.submit2 = function() {
        console.log('User clicked submit with ', self.user2);
      };
    }]);
</script>
</body>
</html>

This example might seem large and complicated at first, but let’s break it down into smaller chunks to see what’s happening:

  • We have two forms within our body, both with two input fields: a username and password text box. The username field in both forms has three validators: an email validator (by specifying the type as email), one to mark it as required, and another to ensure it has a minimum length of four characters. The password field has just the required validator.
  • Before we look at the ng-messages directive, we first look at the end of the body tag. In addition to loading angular.js, we also load angular-messages.js, which gives us the source for the ngMessages module. We also add ngMessages as a dependency to our notesApp module.
  • We have defined a script tag with type text/ng-template and an ID of error-messages. We can use this technique to define HTML partials inline inside our main index.html file. The content of the script tag could have been in a separate HTML file as well.
  • This HTML inside the script tag has our default error messages and the conditions on which to show them. In this particular case, we have the required and minlength error messages defined.
  • Now, we jump back to our forms and the simpleForm first. After each field, we create a new div tag with the ng-messages directive. We pass the $error object to the ng-messages directive based on which we want to show error messages. The ng-messages directive will look for keys within this object, and if it matches a particular condition, that message will be shown in the UI.
  • The ng-messages directive allows us to define the error messages inline directly (as in the case of the password field in overriddenForm), or include an external template using the ng-messages-include attribute along with the ng-messages directive.
  • The ng-messages-include attribute looks for an inline template; that is, a script tag of type text/ng-template with the given id, or a remote file, which will be loaded asynchronously by AngularJS.
  • The order in which we define the messages in the template (or as a child of the ng-messages directive) defines the order in which the error messages are shown. We could simultaneously have two or more errors happening on the same field. In this case, the username field could be less than six characters long, as well as an invalid email address. Since we have defined the minlength ng-message before the email ng-message, the minlength error will be shown first, and if that condition is satisfied, then and only then will the email validation error be shown.
  • We can also override error messages from our common templates. For the username field in the overriddenForm, we override the required error message to be more specific to the use case, while still using the minlength and email error messages from our common templates. ngMessages is smart enough to override just the ones we have defined and reuse the remaining from the common messages.

By default, ngMessages shows only the first error message that is true from the list of ng-message conditions that it has. If in case we need to show all the validations that failed, we can still use the ng-messages directive by simply specifying the ng-messages-multiple attribute. This will ensure that all error conditions that are met will have their messages shown.

The ngMessages module can also be used with any of our custom requirements, and it’s not necessary that it be used only with forms. It just looks at the key values of the given object and displays messages like a switch case statement.

Thus, using the ngMessages module can greatly simplify and clean up how we show and handle error messages for forms in AngularJS in a consistent, reusable manner.

Styling Forms and States

We saw the various states of the forms (and the inputs): $dirty, $valid, and so on. We saw how to display specific error messages and disable buttons based on these conditions, but what if we want to highlight certain input fields or form states using UI and CSS? One option would be to use the form and input states along with the ng-class directive to, say, add a class dirty when myForm.$dirty is true. But AngularJS provides an easier option.

For each of the states we described previously, AngularJS adds and removes the CSS classes shown in Table 4-3 to and from the forms and input elements.

Table 4-3. Form state CSS classes
Form stateCSS class applied

$invalid

ng-invalid

$valid

ng-valid

$pristine

ng-pristine

$dirty

ng-dirty

Similarly, for each of the validators that we add on the input fields, we also get a CSS class in a similarly named fashion, as demonstrated in Table 4-4.

Table 4-4. Input state CSS classes
Input stateCSS class applied

$invalid

ng-invalid

$valid

ng-valid

$pristine

ng-pristine

$dirty

ng-dirty

required

ng-valid-required or ng-invalid-required

min

ng-valid-min or ng-invalid-min

max

ng-valid-max or ng-invalid-max

minlength

ng-valid-minlength or ng-invalid-minlength

maxlength

ng-valid-maxlength or ng-invalid-maxlength

pattern

ng-valid-pattern or ng-invalid-pattern

url

ng-valid-url or ng-invalid-url

email

ng-valid-email or ng-invalid-email

date

ng-valid-date or ng-invalid-date

number

ng-valid-number or ng-invalid-number

Other than the basic input states, AngularJS takes the name of the validator (number, maxlength, pattern, etc.) and depending on whether or not that particular validator has been satisfied, adds the ng-valid-validator_name or ng-invalid-validator_name class, respectively.

Let’s take an example of how this might be used to highlight the input in different ways:

<!-- File: chapter4/form-styling.html -->
<html ng-app="notesApp">
<head>
  <title>Notes App</title>
  <style>
    .username.ng-valid {
      background-color: green;
    }
    .username.ng-dirty.ng-invalid-required {
      background-color: red;
    }
    .username.ng-dirty.ng-invalid-minlength {
      background-color: lightpink;
    }
  </style>
</head>
<body ng-controller="MainCtrl as ctrl">

  <form ng-submit="ctrl.submit()" name="myForm">
    <input type="text"
           class="username"
           name="uname"
           ng-model="ctrl.user.username"
           required
           ng-minlength="4">
    <input type="submit"
           value="Submit"
           ng-disabled="myForm.$invalid">
  </form>

<script
  src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.11/angular.js">
</script>
<script type="text/javascript">
  angular.module('notesApp', [])
    .controller('MainCtrl', [function() {
      var self = this;
      self.submit = function() {
        console.log('User clicked submit with ', self.user);
      };
    }]);
</script>
</body>
</html>

In this example, we kept the existing functionality of the validators, though we removed the specific error messages. Instead, what we try to do is mark out the required field using CSS classes. So here is what the example accomplishes:

  • When the field is correctly filled out, it turns the input box green. This is done by setting the background color when the CSS class ng-valid is applied to our input field.
  • We want to display the background as dark red if the user starts typing in, and then undoes it. That is, we want to set the background as red, marking it as a required field, but only after the user modifies the field. So we set the background color to be red if the CSS classes ng-dirty (which marks that the user has modified it) and ng-invalid-minlength (which marks that the user has not typed in the necessary amount of characters) are applied.

Similarly, you could add a CSS class that shows a * mark in red if the field is required but not dirty. Using a combination of these CSS classes (and the form and input states) from before, you can easily style and display all the relevant and actionable things to the user about your form.

ngModelOptions

As you might have noticed with ng-model, any keystroke causes the AngularJS model to immediately update and refresh the UI. This actually causes an entire UI update (known as the digest cycle, covered more in-depth in Chapter 13). But we might not want this behavior. We might instead want to update the model when the user stops typing for half a second, or moves on to the next input element, thus saving some UI update cycles.

With AngularJS 1.3, we can now set up and define exactly how the ng-model directive works and updates the UI. With ng-model-options, we can set the following options for how ng-model works:

updateOn
This is a string that dictates which events of the input should the ng-model directive listen on and update. You can specify default to tell it to listen on the default events of the control (keypress for text boxes, click for checkboxes, and so on), or specify your own, like blur. You can give multiple events space separated to this option as well.
debounce
This can be either an integer value or an object. debounce sets the period of time in milliseconds AngularJS should wait for the user to stop performing an action before updating the AngularJS model variable. If this is an object instead of an integer, then we can specify debounce for each individual event specified in updateOn, like {"default": 500, "blur": 0} for a text box. This directs AngularJS to update the text box when the user stops typing for half a second, or moves away from the input field. A value of 0 tells AngularJS to update it immediately.
allowInvalid
false by default, AngularJS will not set the value in the ng-model variable if the input field is invalid as per the validators present. If we want to set the value regardless of the state of the validator, we can set the allowInvalid option to true.
getterSetter
This is a boolean value that allows us to treat the ng-model expression as a getter/setter instead of a variable.

Let’s look at an example of the ng-model-options in action:

<!-- File: chapter4/ng-model-options.html -->
<html ng-app="modelApp">
<head><title>Ng Model Options</title></head>
<body ng-controller="MainCtrl as ctrl">

  <div>
    <input type="text"
           ng-model="ctrl.withoutModelOptions"
           ng-minlength="5"/>
    You typed {{ctrl.withoutModelOptions}}
  </div>
  <div>
    <input type="text"
           ng-model="ctrl.withModelOptions"
           ng-model-options="ctrl.modelOptions"
           ng-minlength="5"/>
    You typed {{ctrl.withModelOptions()}}
  </div>

<script
  src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.11/angular.js">
</script>
<script type="text/javascript">
  angular.module('modelApp', [])
    .controller('MainCtrl', [function() {
      this.withoutModelOptions = '';
      var _localVar = '';
      this.modelOptions = {
        updateOn: 'default blur',
        debounce: {
          default: 1000,
          blur: 0
        },
        getterSetter: true,
        allowInvalid: true
      };
      this.withModelOptions = function(txt) {
        if (angular.isDefined(txt)) {
          _localVar = txt;
        } else {
          return _localVar;
        }
      };
    }]);
</script>
</body>
</html>

In this example, we have two input fields. The first one is bound using ng-model to the variable withoutModelOptions. There is a validation applied on it to ensure it contains at least five characters. We also see the value of it in the UI through a binding expression immediately after that (which only triggers after we have typed in five characters).

The second model is bound to a getter/setter function (withModelOptions). Now normally, this would not work with ng-model, which is why we also specify an ng-model-options along with it. Now the value passed to ng-model-options is an object that can be defined directly in the HTML, or as in this case, refer to a variable from our controller in case of complex options. As part of our ng-model-options, we have set it to:

  • Bind on the default type events and the blur event
  • Set the debounce time to 1 second in case of type, and immediate in case of a blur event
  • Bind to a getter and setter that is defined in our controller, instead of a variable
  • Allow invalids, which ensures that even if the form field is invalid, we still have access to the invalid value in our controller

Note

You can also ådd an ngModelOptions to a higher-level element (say, the form, or the topmost element containing the entire AngularJS application) to have it apply as default to all your ngModel in your application, instead of repeating it for each element with the ngModel directive.

Nested Forms with ng-form

By this point, we know how to create forms and get data into and out of our controllers (by binding it to a model). We have also seen how to perform simple validation, and style and display conditional error messages in AngularJS.

The next part that we want to cover is how to deal with more complicated form structures, and grouping of elements. We sometimes run into cases where we need subsections of our form to be valid as a group, and to check and ascertain its validity. This is not possible with the HTML form tag because form tags are not meant to be nested.

AngularJS provides an ng-form directive, which acts similar to form but allows nesting, so that we can accomplish the requirement of grouping related form fields under sections:

<!-- File: chapter4/nested-forms.html -->
<html ng-app>
<head>
  <title>Notes App</title>
</head>

<body>
  <form novalidate name="myForm">
    <div>
      <input type="text"
             class="username"
             name="uname"
             ng-model="ctrl.user.username"
             required=""
             placeholder="Username"
             ng-minlength="4" />
      <input type="password"
             class="password"
             name="pwd"
             ng-model="ctrl.user.password"
             placeholder="Password"
             required="" />
    </div>

    <ng-form name="profile">
      <input type="text"
             name="firstName"
             ng-model="ctrl.user.profile.firstName"
             placeholder="First Name"
             required>
      <input type="text"
             name="middleName"
             placeholder="Middle Name"
             ng-model="ctrl.user.profile.middleName">
      <input type="text"
             name="lastName"
             placeholder="Last Name"
             ng-model="ctrl.user.profile.lastName"
             required>
      <input type="date"
             name="dob"
             placeholder="Date Of Birth"
             ng-model="ctrl.user.profile.dob">
    </ng-form>

    <span ng-show="myForm.profile.$invalid">
      Please fill out the profile information
    </span>

    <input type="submit"
           value="Submit"
           ng-disabled="myForm.$invalid"/>
  </form>

<script
    src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.11/angular.js">
</script>
</body>

</html>

In this example, we nest a subform inside our main form, but because the HTML form element cannot be nested, we use the ng-form directive to do it. Now we can have substate within our form, evaluate quickly if each section is valid, and leverage the same binding and form states that we have looked at so far. A quick highlight of the features in the example:

  • A subform using the ng-form directive. We can give this a name to identify and grab the state of the subform.
  • The state of the subform can be accessed directly (profile.$invalid) or through the parent form (myForm.profile.$invalid).
  • Individual elements of the form can be accessed as normal (profile.firstName.$error.required).
  • Subforms and nested forms still affect the outer form (the myForm.$invalid is true because of the use of the required tags).

You could have subforms and groupings that have their own way of checking and deciding validity, and ng-form allows you to model that grouping in your HTML.

Other Form Controls

We have dealt with forms, ng-models, and bindings, but mostly we have only looked at regular text boxes. Let’s see how to interact and work with other form elements in AngularJS.

Textareas

Textareas in AngularJS work exactly the same as text inputs. That is, to have two-way data-binding with a textarea, and make it a required field, you would do something like:

<textarea ng-model="ctrl.user.address" required></textarea>

All the data-binding, error states, and CSS classes remain as we saw it with text inputs.

Checkboxes

Checkboxes are in some ways easier to deal with because they can only have one of two values: true or false. So an ng-model two-way data-binding to the checkbox basically takes a Boolean value and assigns the checked state based on it. After that, any changes to the checkbox toggles the state of the model:

<input type="checkbox" ng-model="ctrl.user.agree">

But what if we didn’t have just Boolean values? What if we wanted to assign the string YES or NO to our model, or have the checkbox checked when the value is YES? AngularJS gives two attribute arguments to the checkbox that allow us to specify our custom values for the true and false values. We could accomplish this as follows:

<input type="checkbox"
           ng-model="ctrl.user.agree"
           ng-true-value="'YES'"
           ng-false-value="'NO'">

This sets the value of the agree field to YES if the user checks the checkbox, and NO if the user unchecks it.

Warning

Notice the single quotes around YES and NO. ng-true-value and ng-false-value, as of AngularJS 1.3, take constant expressions as arguments. Prior to AngularJS 1.3, we could specify the true and false values directly like ng-true-value="YES". As of AngularJS 1.3, though, to be consistent with the rest of the AngularJS directives, ng-true-value and ng-false-value take AngularJS constant expressions only.

Do note that you cannot currently pass in a variable reference to ng-true-value or ng-false-value. They only take constant strings. You cannot refer to a controller variable for ng-true-value or ng-false-value.

But what if we didn’t want the two-way data-binding, and just want to use the checkbox to display the current value of a Boolean? That is, one-way data-binding where the state of the checkbox changes when the value behind it changes, but the value doesn’t change on checking or unchecking the checkbox.

We can accomplish this using the ng-checked directive, which binds to an AngularJS expression. Whenever the value is true, AngularJS will set the checked property for the input element, and remove and unset it when the value is false. Let’s use the following example to demonstrate all these together:

<!-- File: chapter4/select-example.html -->
<html ng-app="notesApp">
<head><title>Notes App</title></head>
<body ng-controller="MainCtrl as ctrl">
  <div>
    <h2>What are your favorite sports?</h2>
    <div ng-repeat="sport in ctrl.sports">
      <label ng-bind="sport.label"></label>
      <div>
        With Binding:
        <input type="checkbox"
                   ng-model="sport.selected"
                   ng-true-value="'YES'"
                   ng-false-value="'NO'">
      </div>
      <div>
        Using ng-checked:
        <input type="checkbox"
               ng-checked="sport.selected === 'YES'">
      </div>
      <div>
        Current state: {{sport.selected}}
      </div>
    </div>
  </div>

<script
  src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.11/angular.js">
</script>
<script type="text/javascript">
  angular.module('notesApp', [])
    .controller('MainCtrl', [function() {
      var self = this;
      self.sports = [
        {label: 'Basketball', selected: 'YES'},
        {label: 'Cricket', selected: 'NO'},
        {label: 'Soccer', selected: 'NO'},
        {label: 'Swimming', selected: 'YES'}
      ];
    }]);
</script>
</body>
</html>

With this example, we have an ng-repeat, which has a checkbox with ng-model, a checkbox with ng-checked, and a div with the current state bound to it. The first checkbox uses the traditional two-way data-binding with the ng-model directive. The second checkbox uses ng-checked. This means that:

  • When the user checks the first checkbox, the value of selected becomes YES because the true value, set using ng-true-value, is YES. This triggers the ng-checked and sets the second box as checked (or unchecked).
  • When the user unchecks the first box, the value of selected is set to NO because of the ng-false-value.
  • The second checkbox in each repeater element displays the state of the ng-model using ng-checked. This updates the state of the checkbox whenever the model backing ng-model changes. Checking or unchecking the second checkbox itself has no effect on the value of the model.

So if you need two-way data-binding, use ng-model. If you need one-way data-binding with checkboxes, use ng-checked.

Radio Buttons

Radio buttons behave similarly to checkboxes, but are slightly different. You can have multiple radio buttons (and you normally do) that each assigns a different value to a model depending on which one is selected. You can specify the value using the traditional value attribute of the input element. Let’s see how that would look:

<div ng-init="user = {gender: 'female'}">
    <input type="radio"
           name="gender"
           ng-model="user.gender"
           value="male">
    <input type="radio"
           name="gender"
           ng-model="user.gender"
           value="female">
</div>

In this example, we have two radio buttons. We gave them both the same name so that when one is selected, the other gets deselected. Both of them are bound to the same ng-model (user.gender). Next, each of them has a value, which is the value that gets stored in user.gender (male if it is the first radio button; female, otherwise). Finally, we have an ng-init block surrounding it, which sets the value of user.gender to be female by default. This has the effect of ensuring that the second checkbox is selected when this snippet of HTML loads.

But what if our values are dynamic? What if the value we needed to assign was decided in our controller, or some other place? For that, AngularJS gives you the ng-value attribute, which you can use along with the radio buttons. ng-value takes an AngularJS expression, and the return value of the expression becomes the value that is assigned to the model:

<div ng-init="otherGender = 'other'">
    <input type="radio"
           name="gender"
           ng-model="user.gender"
           value="male">Male
    <input type="radio"
           name="gender"
           ng-model="user.gender"
           value="female">Female
    <input type="radio"
           name="gender"
           ng-model="user.gender"
           ng-value="otherGender">{{otherGender}}
</div>

In this example, the third option box takes a dynamic value. In this case, we assign it as part of the initialization block (ng-init), but in a real application, the initialization could be done from within a controller instead of in the HTML directly. When we say ng-value="otherGender", it doesn’t assign otherGender as a string to user.gender, but the value of the otherGender variable, which is other.

Combo Boxes/Drop-Downs

The final HTML form element (which can be used outside forms as well) is the select box, or the drop-down/combo box as it is commonly known. Let’s take a look at the simplest way you can use select boxes in AngularJS:

<div ng-init="location = 'India'">
    <select ng-model="location">
      <option value="USA">USA</option>
      <option value="India">India</option>
      <option value="Other">None of the above</option>
    </select>
</div>

In this example, we have a simple select box that is data-bound to the variable location. We also initialize the value of location to India, so when the HTML loads, India is the selected option. When the user selects any of the other options, the value of the value attribute gets assigned to the ng-model. The standard validators and states also apply to this field, so those can be applied (required, etc.).

This has a few restrictions, though:

  • You need to know the values in the drop-down up front.
  • They need to be hardcoded.
  • The values can only be strings.

In a truly dynamic app, one or none of these might be true. In such a case, the select HTML element also has a way of dynamically generating the list of options, and working with objects instead of pure string ng-models. This is done through the ng-options directive. Let’s take a look at how this might work:

<!-- File: chapter4/checkbox-example.html -->
<html ng-app="notesApp">
<head><title>Notes App</title></head>
<body ng-controller="MainCtrl as ctrl">

<div>
  <select ng-model="ctrl.selectedCountryId"
          ng-options="c.id as c.label for c in ctrl.countries">
  </select>
  Selected Country ID : {{ctrl.selectedCountryId}}
</div>

<div>
  <select ng-model="ctrl.selectedCountry"
          ng-options="c.label for c in ctrl.countries">
  </select>

  Selected Country : {{ctrl.selectedCountry}}
</div>

<script
  src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.11/angular.js">
</script>
<script type="text/javascript">
  angular.module('notesApp', [])
    .controller('MainCtrl', [function() {
      this.countries = [
        {label: 'USA', id: 1},
        {label: 'India', id: 2},
        {label: 'Other', id: 3}
      ];
      this.selectedCountryId = 2;
      this.selectedCountry = this.countries[1];
    }]);
</script>
</body>
</html>

In this example, we have two select boxes, both bound to different models in our controller. The first select element is bound to ctrl.selectedCountryId and the second one is bound to ctrl.selectedCountry. Note that one is a number, while the other is an actual object. How do we achieve this?

  • We use the ng-options attribute on the select dialog, which allows us to repeat an array (or object, similar to ng-repeat from Working with and Displaying Arrays) and display dynamic options.
  • The syntax is similar to ng-repeat as well, with some additional ability to select what is displayed as the label, and what is bound to the model.
  • In the first select box, we have ng-options="c.id as c.label for c in ctrl.countries". This tells AngularJS to create one option for each country in the array of countries. The syntax is as follows: modelValue as labelValue for item in array. In this case, we tell AngularJS that our modelValue is the ID of each element, the label value is the label key of each array item, and then our typical for each loop.
  • In the second select box, we have ng-options="c.label for c in ctrl.countries". Here, when we omit the modelValue, AngularJS assumes that each item in the repeat is the actual model value, so when we select an item from the second select box, the country object (c) of that option box gets assigned to ctrl.selectedCountry.
  • Because the backing model for the two select boxes are different, changing one does not affect the value or the selection in the other drop-down.
  • You can also optionally give a grouping clause, for which the syntax would be ng-options="modelValue as labelValue group by groupValue for item in array". Similar to how we specified the model and label values, we can point the groupValue at another key in the object (say, continent).
  • When you use objects, the clause changes as follows: modelValue as labelValue group by groupValue for (key, value) in object.

Note

AngularJS compares the ng-options individual values with the ng-model by reference. Thus, even if the two are objects that have the same keys and values, AngularJS will not show that item as selected in the drop-down unless and until they are the same object. We accomplished this in our example by using an item from the array countries to assign the initial value of the model.

There is a better way to accomplish this, which is through the use of the track by syntax with ng-options. We could have written the ng-options as:

ng-options="c.label for c in ctrl.countries track by c.id"

This would ensure that the object c is compared using the ID field, instead of by reference, which is the default.

Conclusion

We started with the most common requirements, which is getting data in and out of UI forms. We played around with ng-model, which gives us two-way data-binding to remove most of the boilerplate code we would write when working with forms. We then saw how we could leverage form validation, and show and style error messages. Finally, we saw how to deal with other types of form elements, and the kinds of options AngularJS gives to work with them.

In the next chapter, we start dealing with AngularJS services and then jump into server communication using the $http service in AngularJS.

Get AngularJS: Up and Running now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.