How to create custom validator directives with AngularJS

Algotech Solutions
The Engineering Publication
7 min readMar 23, 2016

--

This article we will show you how to use AngularJS 1.3.x features (or later) to create a form with special requirements like the one for creating a new account on a server. Furthermore, ngMessages will be used to properly display any error messages.

As you may have already guessed, a minimal account creation form requires at least two or three fields: a username, a password, and a password match field. While the user is typing in the username field, it would be nice if the field validation would keep in touch with the server and interactively check if it’s current value already exists or not and inform the user about this. On the other hand, this checking should not be too often as this may “stress” the user.

The create account request should only be made if all the fields of the form are valid.

This article assumes that you are already familiar with Angular’s FormController and you are aware of the ngModelController‘s existence.

Brief description about how validators work

Angular instantiates a FormController for each HTML form tag. This form controller is private by default and it can not be referred from any outer controller unless the programmer specifies a form name using name attribute as on the form element.

Each input field with a ng-model attribute on it has a ngModelController associated with it. This controller watches for data changes on the input it is associated with. On data change detection it runs a series of parsers, formatters and validators.

A validator works on the information of the input field which is associated with, by checking the field value against a certain restriction.

Each ngModelController contains a list of registered validators, which are stored in a hashtable style in $validators and $asyncValidators objects.

About custom validators

Angular allows the creation of custom element directives so that programmers can significantly increase the power of a simple form.

An Angular custom validator requires to get a hold on the ngModelController of an input field to work with. This is accomplished by using require: ‘ngModel’ in the directive return object, as in the examples above. Also, in order for a validator to be useful we must register it in one of the $validators or $asyncValidators objects.

When an input field changes its value (on certain conditions and timing) all the validators attached to it will be run against the new value. If one or more validations fail they will individually determine the creation of an entry in the $error object associated with the respective input. We will use that to detect which error message to display and when to display it.

A custom validator directive structure would typically look like this:

angular
.module(‘app’)
.directive(‘customValidatorName’, customValidatorDirectiveFunc);
function customValidatorDirectiveFunc() {
return {
restrict: ‘A’,
require: ‘ngModel’,
link: function (scope, element, attr, ngModel) {
// validation callback registration to ngModel
}
}
}

Create a basic HTML form template

Let’s create a named form and two input fields: username, password.

<form name=”newAccountForm”>
<div>
<label>
Username:</label>
<input
type=”text”
name=”usernameField”
placeholder=”username here”
ng-model=”username”/>
</div>
<div>
<label>
Password:</label>
<input
type=”password”
name=”passwordField”
ng-model=”password”
placeholder=”password here”/>
</div>
</form>

Briefly, this form template will generate a logical structure similar to this one:

newAccountForm = {
usernameField: {
$validators: {},
$asyncValidators: {},
$error: []
},
password: {
$validators: {},
$asyncValidators: {},
$error: []
}
}

Let’s create the username validator first.

Create the username availability validator

Outer look:

angular
.module(‘app’)
.directive(‘checkAvailability’, checkAvailabilityFunc);
function checkAvailabilityFunc($http, $q) {
return {
restrict: ‘A’,
require: ‘ngModel’,
link: function(scope, element, attr, ngModel) {
// fetch the call address from directives ‘checkIfAvailable’
var serverAddr = attr.checkAvailability;
ngModel.$asyncValidators.invalidUsername =
function(modelValue, viewValue) {
// validation logic here
}
}
}
}

We want this validator to be treated as an attribute, so we add restrict: ‘A’ to the return object.

Next, we request the ngModelController adding require: ‘ngModel’.

In the link function parameters list we add ngModel (or ngModelCtrl as others prefer) which represents the ngModelController instance attached to the input element we wish to validate.

The next step is to register the validation callback in $asyncValidators. This is achieved easily, by adding a new property to an object.

ngModel.$asyncValidators.invalidUsername = function(…) {…}

Maybe a question arises here:

Why $asyncValidators and not $validators?

The answer is because we will be doing a server http request which we don’t know how long will last until completion, so instead of freezing the user input until we receive the server response we use this validator category and behaviour offered by Angular.

I also added a server address to the validators attribute which I can read using attr.<directive name> like this: attr.invalidUsername.

Now let’s fill in the validation logic.

ngModel.$asyncValidators.invalidUsername = function(modelValue, viewValue) {
var username = viewValue;
var deferred = $q.defer();
// ask the server if this username exists
$http.get(serverAddr, {data: username}).then(
function(response) {
if (response.data.exists) {
deferred.reject();
} else {
deferred.resolve();
}
});
// return the promise of the asynchronous validator
return deferred.promise;
}

Inside the validator function we make the server call. Here I am making the call using $http but this is not mandatory. The serverAddr is the same as mentioned before.

Thing is, this validator must return a promise which will be rejected or resolved by us. You can see I wrapped the server response in another promise which I returned to Angular.

Angular doesn’t care if/what values you pass to your resolve()/reject() call when you decide if a value is valid or not. This aspect will not decide if the validation is passed or not. The decisive thing is only the final status of the promise, which is simply resolved or rejected.

If the promise is rejected, it means that the validation failed and an invalidUsername error property is created in the $error field of the associated input element. If the promise is resolved it means the validation passed and any invalidUsername error will be removed, if any was present until now.

We can’t just return the $http request promise to Angular because Angular simply doesn’t care what the server says… We must do this by our own, so we listen to it’s response and check the response status/message, then decide if the validation has failed or not.

Here is the full directive code, with server call mocked:

angular
.module(‘app’)
.directive(‘checkAvailability’, checkAvailabilityFunc)
.run(serverMock);
function checkAvailabilityFunc($http, $q, $timeout) {
return {
restrict: ‘A’,
require: ‘ngModel’,
link: function(scope, element, attr, ngModel) {
var serverAddr = attr.checkAvailability;
ngModel.$asyncValidators.invalidUsername =
function(modelValue, viewValue) {
var username = viewValue;
var deferred = $q.defer();
// ask the server if this username exists
$http.get(serverAddr, {data: username}).then(
function(response) {
// simulate a server response delay of half a second
$timeout(function() {
if (response.data.exists) {
deferred.reject();
} else {
deferred.resolve();
}
}, 500);
});
// return the promise of the asynchronous validator
return deferred.promise;
}
}
}
}
function serverMock($httpBackend) {
var occupiedUsernames = [“Joe”, “Joee”, “Rogers”, “Kim”];

// mock the server GET username availability request
$httpBackend.whenGET(“localhost:3000/users/”).respond(
function(method, url, data) {
var username = data;
// ‘true’ if username is in array
var exists = occupiedUsernames.indexOf(username) > -1;
return [200, {exists: exists}, {}];
}
);
}

The server mock implementation is not the subject of this demo.

viewValue or modelValue?

Because our input element is of type=”text”, it doesn’t matter.. But if it were a type=”number” input, the difference between viewValue and modelValue would be in the fact that viewValue would see the number as a string of numeric chars while modelValue would see the number string converted into a primitive number.

Enhance username input element

<div>
<label>
Username:</label>
<input
type=”text”
name=”usernameField”
placeholder=”username here”
ng-model=”credentials.username”
minlength=”3"
ng-model-options=”{updateOn: ‘default blur’, debounce: {default: 500, blur: 0}}”
check-availability=”localhost:3000/users/”/>
<span
ng-messages=”newAccountForm.usernameField.$error” ng-show=”newAccountForm.usernameField.$dirty”>
<span
ng-message=”required”>Required username!</span>
<span
ng-message=”minlength”>Too short!</span>
<span
ng-message=”invalidUsername”>Username unavailable! </span>
</span>
</div>

Notice the presence of check-availability attribute with “localhost:3000/users/” value, which corresponds to serverAddr in the validator directive. I also added a built-in validator called minlength, which will trigger a “minlength” error when the username is shorter than 3 characters.

There is also this element: ng-model-options which says: Update the ng-model value once every 500ms when the focus is in the input element, or update instantly when the input focus is lost.

Next you can see a usage example of ngMessages and ngMessage directives, which are also very simple to use. ngMessages has child ngMessage tags which behave similarly to ngShow when an error name is present in the $error array. The difference is that only one ngMessage tag is active at a time (in the default mode) and the priority is from top to bottom.

Note that we only show the error messages when the user has modified something in the input field (i.e. when $dirty is true).

Here is a plunker link to help you save time. I hope you enjoyed this demo. Keep up the good work!

Originally published at www.algotech.solutions on March 18, 2016. Learn more about our stack and what we do at algotech.solutions.

If you enjoyed this article please take a moment and share it with your friends or click the little green heart below. If you want to get our latest content first, subscribe to our newsletter. No spam. Just great engineering posts!

--

--

Algotech Solutions
The Engineering Publication

Algotech is the software development arena where our team fights to develop and deliver projects on time, on budget and on scope.