Using Forms
Every website eventually uses some kind of form for users to enter data. Angular makes it particularly easy to implement client-side form validations to give immediate feedback for an improved user experience.
Implementing a Basic Form
Problem
You wish to create a form to enter user details and capture this information in an Angular.js scope.
Solution
Use the standard form tag and the ng-model directive to implement a basic form:
1 <body ng-app="MyApp">
2 <div ng-controller="User">
3 <form ng-submit="submit()" class="form-horizontal" novalidate>
4 <label>Firstname</label>
5 <input type="text" ng-model="user.firstname"/>
6 <label>Lastname</label>
7 <input type="text" ng-model="user.lastname"/>
8 <label>Age</label>
9 <input type="text" ng-model="user.age"/>
10 <button class="btn">Submit</button>
11 </form>
12 </div>
13 </body>
The novalidate attribute disables the HTML5 validations, which are client-side validations supports by modern browsers. In our example we only want the Angular.js validations running to have complete control over the look and feel.
The controller binds the form data to your user model and implements the submit() function:
1 var app = angular.module("MyApp", []);
2
3 app.controller("User", function($scope) {
4 $scope.user = {};
5 $scope.wasSubmitted = false;
6
7 $scope.submit = function() {
8 $scope.wasSubmitted = true;
9 };
10 });
You can find the complete example on github.
Discussion
The initial idea when using forms would be to implement them in the traditional way by serialising the form data and submit it to the server. Instead we use ng-model to bind the form to our model, something we have been doing a lot already in previous recipes.
The submit button state is reflected in our wasSubmitted scope variable, but no submit to the server was actually done. The default behavior in Angular.js forms is to prevent the default action since we do not want to reload the whole page. We want to handle the submission in an application-specific way. In fact there is even more going on in the background and we are going to look into the behavior of the form or ng-form directive in the next recipe.
Validating a Form Model Client-Side
Problem
You wish to validate the form client-side using HTML5 form attributes.
Solution
Angular.js works in tandem with HTML5 form attributes. Let us start with the same form but let us add some HTML5 attributes to make the input required:
1 <form name="form" ng-submit="submit()">
2 <label>Firstname</label>
3 <input name="firstname" type="text" ng-model="user.firstname" required/>
4 <label>Lastname</label>
5 <input type="text" ng-model="user.lastname" required/>
6 <label>Age</label>
7 <input type="text" ng-model="user.age"/>
8 <br>
9 <button class="btn">Submit</button>
10 </form>
It is still the same form but this time we defined the name attribute on the form and made the input required for the firstname.
Let us add some more debug output below the form:
1 Firstname input valid: {{form.firstname.$valid}}
2 <br>
3 Firstname validation error: {{form.firstname.$error}}
4 <br>
5 Form valid: {{form.$valid}}
6 <br>
7 Form validation error: {{form.$error}}
You can find the complete example on github.
Discussion
When starting with a fresh empty form, you will notice that Angular adds the css class ng-pristine and ng-valid to the form tag and each input tag. When editing the form the ng-pristine class will be removed from the changed input field and also from the form tag. Instead it will be replaced by the ng-dirty class. Very useful because it allows you to easily add new features to your app depending on these states.
In addition to these two css classes there are two more to look into. The ng-valid class will be added whenever an input is valid, otherwise the css class ng-invalid is added. Note that the form tag also gets either a valid or invalid class depending on the input fields. To demonstrate this I’ve added the required HTML5 attribute. Initially, the firstname and lastname input fields are empty and therefore have the ng-invalid css class, whereas the age input field has the ng-valid class. Additionally, there’s ng-invalid-required class alongside the ng-invalid for even more specificity.
Since we defined the name attribute on the form HTML element we can now access Angular’s form controller via scope variables. In the debug output we can check the validity and specific error for each named form input and the form itself. Note that this only works on the level of the form’s name attributes and not on the model scope. If you output the following expression {{user.firstname.$error}} it will not work.
Angular’s form controller exposes $valid, $invalid, $error, $pristine and $dirty variables.
For validation, Angular provides built-in directives including required, pattern, minlength, maxlength, min and max.
Let us use Angular’s form integration to actually show validation errors in the next recipe.
Displaying Form Validation Errors
Problem
You wish to show validation errors to the user by marking the input field red and displaying an error message.
Solution
We can use the ng-show directive to show an error message if a form input is invalid and CSS classes to change the input element’s background color depending on its state.
Let us start with the styling changes:
1 <style type="text/css">
2 input.ng-invalid.ng-dirty {
3 background-color: red;
4 }
5 input.ng-valid.ng-dirty {
6 background-color: green;
7 }
8 </style>
And here is a small part of the form with an error message for the input field:
1 <label>Firstname</label>
2 <input name="firstname" type="text" ng-model="user.firstname" required/>
3 <p ng-show="form.firstname.$invalid && form.firstname.$dirty">
4 Firstname is required
5 </p>
You can find the complete example on github.
Discussion
The CSS classes ensure that we initially show the fresh form without any classes. When the user starts typing in some input for the first time, we change it to either green or red. That is a good example of using the ng-dirty and ng-invalid CSS classes.
We use the same logic in the ng-show directive to only show the error message when the user starts typing for the first time.
Displaying Form Validation Errors with the Twitter Bootstrap framework
Problem
You wish to display form validation errors but the form is styled using Twitter Bootstrap.
Solution
When using the .horizontal-form class Twitter Bootstrap uses div elements to structure label, input fields and help messages into groups. The group div has the class control-group and the actual controls are further nested in another div element with the CSS class controls. Twitter Bootstrap shows a nice validation status when adding the CSS class error on the div with the control-group class.
Let us start with the form:
1 <div ng-controller="User">
2 <form name="form" ng-submit="submit()" novalidate>
3
4 <div class="control-group" ng-class="error('firstname')">
5 <label class="control-label" for="firstname">Firstname</label>
6 <div class="controls">
7 <input id="firstname" name="firstname" type="text"
8 ng-model="user.firstname" placeholder="Firstname" required/>
9 <span class="help-block"
10 ng-show="form.firstname.$invalid && form.firstname.$dirty">
11 Firstname is required
12 </span>
13 </div>
14 </div>
15
16 <div class="control-group" ng-class="error('lastname')">
17 <label class="control-label" for="lastname">Lastname</label>
18 <div class="controls">
19 <input id="lastname" name="lastname" type="text"
20 ng-model="user.lastname" placeholder="Lastname" required/>
21 <span class="help-block"
22 ng-show="form.lastname.$invalid && form.lastname.$dirty">
23 Lastname is required
24 </span>
25 </div>
26 </div>
27
28 <div class="control-group">
29 <div class="controls">
30 <button ng-disabled="form.$invalid" class="btn">Submit</button>
31 </div>
32 </div>
33 </form>
34 </div>
Note that we use the ng-class directive on the control-group div. So let’s look at the controller implementation of the error function:
1 app.controller("User", function($scope) {
2 // ...
3 $scope.error = function(name) {
4 var s = $scope.form[name];
5 return s.$invalid && s.$dirty ? "error" : "";
6 };
7 });
The error function gets the input name attribute passed as a string and checks for the $invalid and $dirty flags to return either the error class or a blank string.
You can find the complete example on github.
Discussion
Again we check both the invalid and dirty flags because we only show the error message in case the user has actually changed the form. Note that this ng-class function usage is pretty typical in Angular since expressions do not support ternary checks.
Only Enabling the Submit Button if the Form is Valid
Problem
You wish to disable the Submit button as long as the form contains invalid data.
Solution
Use the $form.invalid state in combination with a ng-disabled directive.
Here is the changed submit button:
1 <button ng-disabled="form.$invalid" class="btn">Submit</button>
You can find the complete example on github.
Discussion
The Form Controller attributes form.$invalid and friends are very useful to cover all kinds of use cases which focus on the form as a whole instead of individual fields.
Note that you have to assign a name attribute to the form element, otherwise form.$invalid won’t be available.
Implementing Custom Validations
Problem
You wish to validate user input by comparing it to a blacklist of words.
Solution
The angular-ui project offers a nice custom validation directive which lets you pass in options via expression.
Let us have a look at the template first with the usage of the ui-validate Directive:
1 <input name="firstname" type="text"
2 ng-model="user.firstname" required
3 ui-validate=" { blacklisted: 'notBlacklisted($value)' } "
4 />
5
6 <p ng-show='form.firstname.$error.blackListed'>
7 This firstname is blacklisted.
8 </p>
And the controller with the notBlackListed implementation:
1 var app = angular.module("MyApp", ["ui", "ui.directives"]);
2
3 app.controller("User", function($scope) {
4 $scope.blacklist = ['idiot','loser'];
5
6 $scope.notBlackListed = function(value) {
7 return $scope.blacklist.indexOf(value) === -1;
8 };
9 });
You can find the complete example on github.
Discussion
First we need to explicitly list our module dependency to the Angular UI directives module. Make sure you actually download the javascript file and load it via script tag.
Our blacklist contains the words we do not want to accept as user input and the notBlackListed function checks if the user input matches any of the words defined in the blacklist.
The ui-validate directive is pretty powerful since it lets you define your custom validations easily by just implementing the business logic in a controller function.
If you want to know even more, have a look at how to implement custom directives for yourself in Angular’s excellent guide.