Building a Custom AngularJS Unique Value Directive
One of the tasks that comes up fairly frequently in application development is ensuring that a specific value is unique as a user enters it into a form. For example, you may have a user name or email that is entered by a user that you want to ensure is unique before allowing them to submit the form. There are a variety of ways to handle that type of scenario including making standard Ajax calls directly from an AngularJS controller, but if you find yourself performing this type of task over and over it may be time to consolidate your code and make a re-useable component. Enter directives – the perfect way to add “unique value” functionality into a form.
In this post I’m going to walk through a simple unique value directive and show how it can be used. Along the way I’ll also discuss a few aspects of validation in AngularJS as well. An example of it in action can be seen here:
The directive that controls checking for a unique value is applied to an element in the following way (note that I prefer to prefix directives with the HTML5 data- prefix):
<input type="email" name="email" data-ng-model="customer.email" data-wc-unique="{key: customer.id, property: 'email'}" required />
Before jumping into the directive let’s take a look at an AngularJS factory that can be used to check if a value is unique or not.
Creating the Data Service
Unique value checks typically rely on a call to a back-end data store of some type. AngularJS provides services such as $http and $resource that are perfect for that scenario. For this demonstration I’ll use a simple custom factory named dataService that relies on $http to make Ajax calls back to a server. Note that I don’t typically distinguish between the term “factory” and “service” as far as the name goes since they ultimately do the same thing and “service” just sounds better to me (personal preference).
angular.module('customersApp') .factory('dataService', ['$http', function ($http) { var serviceBase = '/api/dataservice/', dataFactory = {}; dataFactory.checkUniqueValue = function (id, property, value) { if (!id) id = 0; return $http.get(serviceBase + 'checkUnique/' + id + '?property=' +
property + '&value=' + escape(value)).then( function (results) { return results.data.status; }); }; return dataFactory; }]);
Creating the Unique Directive
To handle checking unique values I created a custom directive named wcUnique (the wc stands for Wahlin Consulting – my company). It’s a fairly simple directive that is restricted to being used as an attribute. The shell for the directive looks like the following:
angular.module('customersApp') .directive('wcUnique', ['dataService', function (dataService) { return { restrict: 'A', require: 'ngModel', link: function (scope, element, attrs, ngModel) {
} } }]);
Looking through the code you can see that the directive relies on the dataService shown earlier. It restricts the directive to being used as an attribute and requires access to ngModel. Why require ngModel? It’ll be used to access an ngModelController object that sets a validity value depending on whether or not a given value is unique. Every time you use the ngModel directive there’s a little ngModelController helper object working behind the scenes to handle data binding, validation, and more. Here’s what it contains. Take special note of the $error property since it’ll be discussed later.
As the directive is loaded the link function will be called which gives access to the current scope, the element the directive is being applied to, attributes on the element, and the ngModelController object. Here’s the full directive with the contents of the link function included:
angular.module('customersApp') .directive('wcUnique', ['dataService', function (dataService) { return { restrict: 'A', require: 'ngModel', link: function (scope, element, attrs, ngModel) { element.bind('blur', function (e) { if (!ngModel || !element.val()) return; var keyProperty = scope.$eval(attrs.wcUnique); var currentValue = element.val(); dataService.checkUniqueValue(keyProperty.key, keyProperty.property, currentValue) .then(function (unique) { //Ensure value that being checked hasn't changed //since the Ajax call was made if (currentValue == element.val()) { ngModel.$setValidity('unique', unique); } }, function () { //Probably want a more robust way to handle an error //For this demo we'll set unique to true though ngModel.$setValidity('unique', true); }); }); } } }]);
The code within the link function starts off by binding to the blur event of the element. I didn’t want to call back to the server every time a user enters something so blur worked out well in this situation. Once the blur event fires the attributes of the element are accessed. Specifically the value assigned to the wcUnique directive is accessed. AngularJS’s $eval() function is used to convert the value into an object literal that is assigned to a keyProperty variable. Here’s what the keyProperty value looks like:
{key: customer.id, property: 'email'}
The key represents the unique key for the customer object (ultimately the unique identifier for the record). This is used so that we exclude the current customer when checking for a unique value across customers. The property represents the name of the customer object property that should be checked for uniqueness by the back-end system.
Once the keyProperty variable is filled with data, the key and property values are passed along with the element’s value (the value of the textbox for example) to a function named checkUniqueValue() that’s provided by the factory shown earlier. This triggers an Ajax call back to the server which returns a true or false value. Once the value is returned from the back-end service it’s used in a call to ngModel.$setValidity(). As mentioned earlier, ngModel represents a special object which is an ngModelController (you can think of it as a little mini-controller used for data binding, validation, and other functionality) that’s created due to the presence of the ngModel directive shown next. It can be used to determine if a value bound to a control is dirty and if it’s valid or not.
<input type="email" name="email" data-ng-model="customer.email" data-wc-unique="{key: customer.id, property: 'email'}" required />
The $setValidity() function is a built-in AngularJS function that is used to assign a key/value combination that can be used to check the validity of a specific model value. The key in this case is “unique” and the value is either true or false. When the $setValidity() function is called, the unique value is added to an $error object that can be used to check if a value is valid or not.
Showing Error Messages
The unique property added to the $error object can be used to show and hide error messages. In the previous section you saw that the $error object is updated but how do you access the $error object? When using AngularJS forms, a name attribute is typically added to the <form> element as shown next:
<form name="editForm">
The editForm value causes AngularJS to create a child controller named editForm that is associated with the current scope. In other words, editForm is added as a property of the current scope (the scope originally created by your controller). The textbox shown earlier has a name attribute value of “email” which gets converted to a property that is added to the editForm controller. It’s this email property that gets the $error object. Confusing? Let’s look at an example of how we can check the unique value to see if the email address is unique or not which should clear things up:
<input type="text" name="email" data-ng-model="customer.email" data-wc-unique="{key: customer.id, property: 'email'}" required /> <span class="errorMessage" ng-show="editForm.email.$dirty && editForm.email.$error.unique"> Email already in use </span>
Notice that the ngShow directive checks the editForm property of the current scope and then drills down into the email property. It checks if the value is dirty and if the $error.unique value is true or false. The unique value was set using the $setValidity() function shown earlier. If the value is dirty and the unique value is false then the span is hidden. If it’s dirty and the unique value is true then the contents of the span are shown and the user sees that they need to enter a different email address. The end result is the red error message shown next:
Conclusion
Directives provide a great way to encapsulate functionality that can be used in views. In this post you’ve seen a simple AngularJS unique directive that can be applied to textboxes to check if a specific value is unique or not and display a message to the user. To see the directive in an actual application check out the Customer Manager sample application at https://github.com/DanWahlin/CustomerManagerStandard.