Angular Forms Data Model Based Valdiation Approach
This blog post accompanies ndValidation directive's code published on Github
Background:
While working on a fresh,from the ground up MEAN stack project for a client that plans to base multiple products on the same infrastructure, I put a lot of thought into different areas of code organization, recurring patterns and tooling.
One of my conclusions was that form validation takes too much effort on the developers side. Whilst the "Angular way" of doing things is so said to be declarative and easy to understand while skimming the HTML, it is hard to maintain, as it means that every view where a model is edited the developer needs to repeat the cumbersome array of validation directives and validation message elements with the related display logic and of course message text that repeats for each field. That felt wrong to me.
Using ng-messages from angular 1.3 (which at time of writing is in beta stage) simplifies things a little but not as much as I think is needed.
Inspiration
Since I’m coming from PHP background, the data model class seemed to be the right place to define the validation needs for each property, this is the way it works in the PHP frameworks I used.
Having read Ben Teese’s Rich Data Models in Angular JS article and others like Nan-Software’s article on angular Data models I got used to hold methods and init property values on the Model’s factory rather than the controller.
But there was one piece missing in putting validation rules on the model - the directive that will look for them and apply them on each input.
The initial idea on how to do it hit me while I was playing with the bold and all encompassing project of ngActiveRecord which goes a longer way than what I found necessary
PLAN
I started defining what I wanted to achieve :
A data model will carry its validation configuration on it’s prototype, a single directive on the input element will check the model for validation configuration and apply logic for existing rules using the regular angular infrastructure of form validation - ng-model controller methods which will apply relevant classes and affect the global form validity, as well this directive will create the relevant user messages elements with the complimenting display logic and relevant message text.
Another consideration I made is that this directive should not affect performance and should not create unneeded $watchers or redundant scopes.
I knew that most validations I needed are built in into angular but some other features I needed to implement such as custom functions (for server side validity checking) required some custom validation code, since I was already acquainted with ng-model controller methods and ui-utils validate directive for custom validation code I knew I could cater for this to quite easily.
STRUCTURE
My validation infrastructure solution is built by the following:
- ndValidation - a directive that checks the model (or evaluated named source via attribute ) for validation configuration and if found recompiles the directive with relevant validation directives and the relevant validationMessage element.
- ndValidationService - provides a shared object for these directives.
- validationMessage - compiles a standard validation message with translated message text which is triggered by ngModel state
- ngModel - added directive to ngModel which publishes the validity state into the shared ndValidationService
Apart from the obvious angular there are no dependencies, but I recommend adding the following to your project:
- angular-translate - for translation of user messages, can be easily disabled or interchanged to another solution if needed, although I heartily recommend this.
- ui-utils validate - allows for defining custom validation functions, more info in the readme.
the validationConfig is stored in the model prototype using, in our case we were using Restangular for REST so I hooked it up using Restangular.extendModel function but it can be done otherwise.
TIP
One thing one must remember taking this approach is that for user-input based objects (new entries not existing in the DB) the controller instantiates the scope with new data model (from our factory) so that the validation config is there.
So instead of having in the controller:
$scope.newUser = {};
and in the view:
<input ng-model=’user.name’ required min-length=’5’ ng-pattern=’/[a-z]*/’/>
we have the factory:
angular.module(‘user’).factory(‘userModel’,function userFactory(data){
function User(){
this.name=data.name || ‘’;
}
User.prototype.$validationConfig = {
name:[{required:true,message:’The name field is required’}]
}
return User;
});
and in the controller:
$scope.newUser = new UserModel();
and in the view:
<input ng-model=’user.name’ nd-validation/>
Written by Nadav Sinai
Related protips
1 Response
ngActiveRecord is wonderful, will try it. but i think that's better to define validation rules in model and made it independent of directives. because for example you can have <customdirective></customdirective> that will not understand your ng-validation attribute