AngularJS offers client-side form validation. AngularJS monitors the state of the form and input fields (input, textarea, select), and lets you notify the user about the current state. AngularJS also holds information about whether they have been touched, or modified, or not. You can use standard HTML5 attributes to validate input, or you can make your own validation functions. Client-side validation cannot alone secure user input. Server side validation is also necessary.
Required
Use the HTML5 attribute required to specify that the input field must be filled out:
Example
The input field is required:
<form name=”myForm”>
<input name=”myInput” ng-model=”myInput” required>
</form>
<p>The input’s valid state is:</p>
<h1>{{myForm.myInput.$valid}}</h1>
Use the HTML5 type email to specify that the value must be an e-mail:
Example
The input field has to be an e-mail:
<form name=”myForm”>
<input name=”myInput” ng-model=”myInput” type=”email”>
</form>
<p>The input’s valid state is:</p>
<h1>{{myForm.myInput.$valid}}</h1>
Form State and Input State
AngularJS is constantly updating the state of both the form and the input fields. Input fields have the following states:
- $untouched The field has not been touched yet
- $touched The field has been touched
- $pristine The field has not been modified yet
- $dirty The field has been modified
- $invalid The field content is not valid
- $valid The field content is valid
They are all properties of the input field, and are either true or false. Forms have the following states:
- $pristine No fields have been modified yet
- $dirty One or more have been modified
- $invalid The form content is not valid
- $valid The form content is valid
- $submitted The form is submitted
They are all properties of the form, and are either true or false. You can use these states to show meaningful messages to the user. Example, if a field is required, and the user leaves it blank, you should give the user a warning:
Example
Show an error message if the field has been touched AND is empty:
<input name=”myName” ng-model=”myName” required>
<span ng-show=”myForm.myName.$touched && myForm.myName.$invalid”>The name is required.</span>
CSS Classes
AngularJS adds CSS classes to forms and input fields depending on their states. The following classes are added to, or removed from, input fields:
- ng-untouched The field has not been touched yet
- ng-touched The field has been touched
- ng-pristine The field has not been modified yet
- ng-dirty The field has been modified
- ng-valid The field content is valid
- ng-invalid The field content is not valid
- ng-valid-key One key for each validation. Example: ng-valid-required, useful when there are more than one thing that must be validated
- ng-invalid-key Example: ng-invalid-required
The following classes are added to, or removed from, forms:
- ng-pristine No fields has not been modified yet
- ng-dirty One or more fields has been modified
- ng-valid The form content is valid
- ng-invalid The form content is not valid
- ng-valid-key One key for each validation. Example: ng-valid-required, useful when there are more than one thing that must be validated
- ng-invalid-key Example: ng-invalid-required
The classes are removed if the value they represent is false.
Add styles for these classes to give your application a better and more intuitive user interface.
Example
Apply styles, using standard CSS:
<style>
input.ng-invalid {
background-color: pink;
}
input.ng-valid {
background-color: lightgreen;
}
</style>
Forms can also be styled:
Example
Apply styles for unmodified (pristine) forms, and for modified forms:
<style>
form.ng-pristine {
background-color: lightblue;
}
form.ng-dirty {
background-color: pink;
}
</style>
Custom Validation
AngularJS provides basic implementation for most common HTML5 input types: (text, number, url, email, date, radio, checkbox), as well as some directives for validation (required, pattern, minlength, maxlength, min, max).
With a custom directive, you can add your own validation functions to the $validators object on the ngModelController. To get a hold of the controller, you require it in the directive as shown in the example below.
Each function in the $validators object receives the modelValue and the viewValue as parameters. AngularJS will then call $setValidity internally with the function’s return value (true: valid, false: invalid). The validation functions are executed every time an input is changed ($setViewValue is called) or whenever the bound model changes. Validation happens after successfully running $parsers and $formatters, respectively. Failed validators are stored by key in ngModelController.$error.
Additionally, there is the $asyncValidators object which handles asynchronous validation, such as making an $http request to the backend. Functions added to the object must return a promise that must be resolved when valid or rejected when invalid. In-progress async validations are stored by key in ngModelController.$pending.
Since AngularJS itself uses $validators, you can easily replace or remove built-in validators, should you find it necessary.
To create your own validation function is a bit more tricky; You have to add a new directive to your application, and deal with the validation inside a function with certain specified arguments.
Example
Create your own directive, containing a custom validation function, and refer to it by using my-directive. The field will only be valid if the value contains the character “e”:
<form name=”myForm”>
<input name=”myInput” ng-model=”myInput” required my-directive>
</form>
<script>
var app = angular.module(‘myApp’, []);
app.directive(‘myDirective’, function() {
return {
require: ‘ngModel’,
link: function(scope, element, attr, mCtrl) {
function myValidation(value) {
if (value.indexOf(“e”) > -1) {
mCtrl.$setValidity(‘charE’, true);
} else {
mCtrl.$setValidity(‘charE’, false);
}
return value;
}
mCtrl.$parsers.push(myValidation);
}
};
});
</script>
Example Explained – In HTML, the new directive will be referred to by using the attribute my-directive. In the JavaScript we start by adding a new directive named myDirective. Remember, when naming a directive, you must use a camel case name, myDirective, but when invoking it, you must use – separated name, my-directive. Then, return an object where you specify that we require ngModel, which is the ngModelController. Make a linking function which takes some arguments, where the fourth argument, mCtrl, is the ngModelController,
Then specify a function, in this case named myValidation, which takes one argument, this argument is the value of the input element. Test if the value contains the letter “e”, and set the validity of the model controller to either true or false. At last, mCtrl.$parsers.push(myValidation); will add the myValidation function to an array of other functions, which will be executed every time the input value changes.
Example
<!DOCTYPE html>
<html>
<script src=”https://ajax.googleapis.com/ajax/libs/angularjs/1.6.9/angular.min.js”></script>
<body>
<h2>Validation Example</h2>
<form ng-app=”myApp” ng-controller=”validateCtrl”
name=”myForm” novalidate>
<p>Username:<br>
<input type=”text” name=”user” ng-model=”user” required>
<span style=”color:red” ng-show=”myForm.user.$dirty && myForm.user.$invalid”>
<span ng-show=”myForm.user.$error.required”>Username is required.</span>
</span>
</p>
<p>Email:<br>
<input type=”email” name=”email” ng-model=”email” required>
<span style=”color:red” ng-show=”myForm.email.$dirty && myForm.email.$invalid”>
<span ng-show=”myForm.email.$error.required”>Email is required.</span>
<span ng-show=”myForm.email.$error.email”>Invalid email address.</span>
</span>
</p>
<p>
<input type=”submit”
ng-disabled=”myForm.user.$dirty && myForm.user.$invalid ||
myForm.email.$dirty && myForm.email.$invalid”>
</p>
</form>
<script>
var app = angular.module(‘myApp’, []);
app.controller(‘validateCtrl’, function($scope) {
$scope.user = ‘Demo User’;
$scope.email = [email protected]’;
});
</script>
</body>
</html>
The HTML form attribute novalidate is used to disable default browser validation.
Example Explained – The AngularJS directive ng-model binds the input elements to the model. The model object has two properties: user and email. Because of ng-show, the spans with color:red are displayed only when user or email is $dirty and $invalid.
Implementing custom form controls (using ngModel)
AngularJS implements all of the basic HTML form controls (input, select, textarea), which should be sufficient for most cases. However, if you need more flexibility, you can write your own form control as a directive.
In order for custom control to work with ngModel and to achieve two-way data-binding it needs to:
- implement $render method, which is responsible for rendering the data after it passed the NgModelController.$formatters,
- call $setViewValue method, whenever the user interacts with the control and model needs to be updated. This is usually done inside a DOM Event listener.
Stateful components
Let’s define what we’d call a “stateful component”.
- Fetches state, essentially communicating to a backend API through a service
- Does not directly mutate state
- Renders child components that mutate state
- Also referred to as smart/container components
An example of a stateful component, complete with its low-level module definition (this is only for demonstration, so some code has been omitted for brevity):
/* —– todo/todo.component.js —– */
import templateUrl from ‘./todo.html’;
export const TodoComponent = {
templateUrl,
controller: class TodoComponent {
constructor(TodoService) {
‘ngInject’;
this.todoService = TodoService;
}
$onInit() {
this.newTodo = {
title: ”,
selected: false
};
this.todos = [];
this.todoService.getTodos().then(response => this.todos = response);
}
addTodo({ todo }) {
if (!todo) return;
this.todos.unshift(todo);
this.newTodo = {
title: ”,
selected: false
};
}
}
};
/* —– todo/todo.html —– */
<div class=”todo”>
<todo-form
todo=”$ctrl.newTodo”
on-add-todo=”$ctrl.addTodo($event);”></todo-form>
<todo-list
todos=”$ctrl.todos”></todo-list>
</div>
/* —– todo/todo.module.js —– */
import angular from ‘angular’;
import { TodoComponent } from ‘./todo.component’;
import ‘./todo.scss’;
export const TodoModule = angular
.module(‘todo’, [])
.component(‘todo’, TodoComponent)
.name;
This example shows a stateful component, that fetches state inside the controller, through a service, and then passes it down into stateless child components. Notice how there are no Directives being used such as ng-repeat and friends inside the template. Instead, data and functions are delegated into <todo-form> and <todo-list> stateless components.
Stateless components
Let’s define what we’d call a “stateless component”.
- Has defined inputs and outputs using bindings: {}
- Data enters the component through attribute bindings (inputs)
- Data leaves the component through events (outputs)
- Mutates state, passes data back up on-demand (such as a click or submit event)
- Doesn’t care where data comes from – it’s stateless
- Are highly reusable components
- Also referred to as dumb/presentational components
An example of a stateless component (let’s use <todo-form> as an example), complete with its low-level module definition (this is only for demonstration, so some code has been omitted for brevity):
/* —– todo/todo-form/todo-form.component.js —– */
import templateUrl from ‘./todo-form.html’;
export const TodoFormComponent = {
bindings: {
todo: ‘<‘,
onAddTodo: ‘&’
},
templateUrl,
controller: class TodoFormComponent {
constructor(EventEmitter) {
‘ngInject’;
this.EventEmitter = EventEmitter;
}
$onChanges(changes) {
if (changes.todo) {
this.todo = Object.assign({}, this.todo);
}
}
onSubmit() {
if (!this.todo.title) return;
// with EventEmitter wrapper
this.onAddTodo(
this.EventEmitter({
todo: this.todo
})
);
// without EventEmitter wrapper
this.onAddTodo({
$event: {
todo: this.todo
}
});
}
}
};
/* —– todo/todo-form/todo-form.html —– */
<form name=”todoForm” ng-submit=”$ctrl.onSubmit();”>
<input type=”text” ng-model=”$ctrl.todo.title”>
<button type=”submit”>Submit</button>
</form>
/* —– todo/todo-form/todo-form.module.js —– */
import angular from ‘angular’;
import { TodoFormComponent } from ‘./todo-form.component’;
import ‘./todo-form.scss’;
export const TodoFormModule = angular
.module(‘todo.form’, [])
.component(‘todoForm’, TodoFormComponent)
.value(‘EventEmitter’, payload => ({ $event: payload }))
.name;
Note how the <todo-form> component fetches no state, it simply receives it, mutates an Object via the controller logic associated with it, and passes it back to the parent component through the property bindings. In this example, the $onChanges lifecycle hook makes a clone of the initial this.todo binding Object and reassigns it, which means the parent data is not affected until we submit the form, alongside one-way data flow new binding syntax ‘<‘.