O'Reilly logo

AngularJS: Up and Running by Brad Green, Shyam Seshadri

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

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.2.19/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.2.19/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.

Let’s now build on this, and see how we can integrate 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.2.19/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.2.19/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.2.19/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.

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).

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.2.19/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.

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.2.19/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.

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">
    <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="" />


    <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.2.19/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.

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/checkbox-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.2.19/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/select-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.2.19/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.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required