AngularJS–Part 16, Forms

A list of all previous posts in this series about Angular can be found here.

Introduction

Most applications offer some way of adding or changing data to the user. In this post I want to discuss data entry in an Angular application and analyze what Angular has to offer us in this regard. It turns out that Angular does not reinvent the wheel but rather augment the existing features for data entry provided by HTML5. In a typical HTML page we use the form tag to define an area which is used to add new or change existing data. Inside the form tag we can then use input tags of various types (text, number, date, checkbox, etc.) to edit individual pieces of data.

As a starting point let’s create a very simple data entry form in pure HTML5. This sample is a login form where the user has to enter a username and a password and where there is a login button she can click.

<form>
  <label>User name:</label>
  <input name='userName' type='text'/>
  <br>
  <label>Password:</label>
  <input name='password' type='password'/>
  <br>
  <button>Submit</button>
</form>


Angular Forms

Angular augments the functionality provided by the HTML5 form significantly. If we give the form a name (not an id!) Angular will provide us programmatic access to the form via the scope of the controller which governs the region where the form is located. If we name our form loginForm we then can access it via $scope.loginForm. The loginForm itself is then a JavaScript object which exposes various useful properties or features of the form.

Validating a form

Most importantly we have properties $valid and $invalid on the form which tell us whether or not our form in in a valid state. We can now bind to these values in our view. Let’s make the above sample an Angular form now. To be able to have the form in an invalid state we’ll add the HTML5 required attribute to the userName and password input tags.

<!DOCTYPE html>
<html>
<head>
	<title>Angular Form Sample</title>
</head>
<body ng-app>
<div>
	<form name='loginForm' novalidate>
		<label>User name:</label>
		<input type='text' ng-model='login.userName' required/>
		<br>
		<label>Password:</label>
		<input type='password' ng-model='login.password' required/>
		<br>
		<button>Login</button>
	</form>
	<div>
		<h3>Some form properties</h3>
		<div>The form is valid: {{loginForm.$valid}}</div>
		<div>The form is invalid: {{loginForm.$invalid}}</div>
	</div>
</div>
<script src='bower_components/angular/angular.js'></script>
</body>
</html>

Note that I have added the attribute novalidate to the form tag to suppress the normal HTML5 validation and rather rely on the validation through Angular. If we load the above page in our browser we can see that initially the form is invalid since both data entry fields are empty.

image

As soon as we enter something in both fields the form becomes valid which is shown in the section where we display the form properties.

We cannot only determine whether the whole form is valid or not but also does Angular provide us the validity of each individual data entry control on the form. To access the validity of a control we have to name it similar as we did with the form. Each named control then becomes a property on our loginForm object. Let’s use this possibility to display the state of each input control right after the control itself

<form name='loginForm' novalidate>
	<label>User name:</label>
	<input name='userName' type='text' ng-model='login.userName' required/>
	<span>Valid: {{loginForm.userName.$valid}}</span>
	<br>
	<label>Password:</label>
	<input name='password' type='password' ng-model='login.password' required/>
	<span>Valid: {{loginForm.password.$valid}}</span>
	<br>
	<button>Login</button>
</form>

And this is how it should look like in the browser

image

We can now use what we just learned to conditionally display some error message next to each input element if it is invalid. For this we use the ng-hide attribute on a div tag which contains the error message. In the case of the userName input this would look like this

<label>User name:</label>
<input name='userName' type='text' ng-model='login.userName' required/>
<span>Valid: {{loginForm.userName.$valid}}</span>
<div ng-hide='loginForm.userName.$valid' class='error'>
	Must enter user name.</div>

And again in the browser we get something like this if we have a valid and an invalid input control

image

We can of course also keep the login button disabled while the form is invalid by using the ng-disabled directive on the button

<button ng-disabled='loginForm.$invalid'>Login</button>


Is the form dirty?

Angular also provides us the two properties $dirty and $pristine (the opposite of $dirty) to determine whether or not the user has changed some data in the form. Similar to the case of validity discussed above Angular also provides us the dirty state on an individual input control level. Thus loginForm.userName.$dirty tells us whether or not the user has changed the user name.

First let’s use the form level properties and use them in the section where we display the form properties

<div>
	<h3>Some form properties</h3>
	<div>The form is valid: {{loginForm.$valid}}</div>
	<div>The form is invalid: {{loginForm.$invalid}}</div>
	<div>The form is dirty: {{loginForm.$dirty}}</div>
	<div>The form is pristine: {{loginForm.$pristine}}</div>
</div>

Initially our form will be clean or pristine. As soon as the user starts to type something in any of the input fields the form gets dirty. We can reset the dirty status of the form by using the $setPristine() function provided by Angular on our form. Let’s add a button to the form to achieve this.

<button ng-click='loginForm.$setPristine()'>Clear</button>

Currently we are displaying error messages next to each input control which is invalid. This is annoying if the user has not yet visited and changed the appropriate control. We want to only display the error message if the control is dirty and invalid. Using the $dirty flag we can easily achieve this e.g. for the user name control

<div ng-show='loginForm.userName.$invalid && loginForm.userName.$dirty' 
	class='error'>
	Must enter user name.</div>

Note that I have changed the directive from ng-hide to ng-show and I am using the combination of $invalid and $dirty. Now, to be able to see the error text we have to first enter some username such as that the control is dirty and then erase the content of the input.

Summary

In this post I have discussed the Angular form. Angular augments the base form functionality provided by HTML5. It is very easy to determine the validity of the form as a whole as well as of each individual data entry control of the form. We can also determine whether the form is dirty as a whole or whether or not an individual data entry control of the form is dirty or pristine. This post is only scratching at the surface. Many many more advanced scenarios can be handled with ease. In my next post I’ll discuss some of those more advanced scenarios.

Related Articles:

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Gabriel Schenker

Gabriel N. Schenker started his career as a physicist. Following his passion and interest in stars and the universe he chose to write his Ph.D. thesis in astrophysics. Soon after this he dedicated all his time to his second passion, writing and architecting software. Gabriel has since been working for over 12 years as an independent consultant, trainer, and mentor mainly on the .NET platform. He is currently working as chief software architect in a mid-size US company based in Austin TX providing software and services to the pharmaceutical industry as well as to many well-known hospitals and universities throughout the US and in many other countries around the world. Gabriel is passionate about software development and tries to make the life of developers easier by providing guidelines and frameworks to reduce friction in the software development process. Gabriel is married and father of four children and during his spare time likes hiking in the mountains, cooking and reading.
This entry was posted in AngularJS, introduction. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Kenny Eliasson

    Are we still required to use ng-model since we can get the data from formName.inputName? Can you wire it up just using formName?

  • gleb Chermennov

    I don’t know why people think it’s such a good idea to put validation rules in your HTML. How do I test that thing? And what if my validation rule is complex than just required/length/number?

    • gabrielschenker

      I will discuss complex validation in my next post. This post just shows the basics and I wanted to keep it simple. That’s why I did not include any Angular controller etc. Complex validation needs to be testable and will thus be part of the behavior in the controller

      • gleb Chermennov

        so, if I want my property to be a number and I express that in HTML, there’s no way I can write a unit-test for it?

        • gabrielschenker

          I don’t think we need to test whether or not the property is a number on the client as long as the user enters the value via a number only input. You might want to validate the input on the server though since a hacker might have tampered the data