TL;DR
DEMO here
SOURCE here
To put it bluntly I simply can’t say enough good things about Angular.js. I’ve been using it for the last year or so and have found amazing improvements and productivity not to mention I write way less, way simpler code (and its fun)! Lets talk about one area of Angular in particular that I found room for improvement:
Angular.js Form Validation
Form validation in Angular is also amazingly flexible you can easily create fields and apply validation rules to them:
<div ng-form="exampleForm"> <label for="firstName">First Name *</label> <div> <input type="text" id="firstName" name="firstName" ng-model="person.firstName" ng-minlength="2" /> </div> </div>
Above I’ve created the field “firstName” in “exampleForm” so I can now code against “exampleForm.firstName”. This field is marked as having a minimum length of 2 characters.
The magic comes in because Angular.js exposes a rich object model dealing with the form and all fields that live in it. For example I can write something like this to display a validation error message when the field is invalid after the user has started typing with something like this:
<div ng-form="exampleForm"> <label for="firstName">First Name *</label> <div> <input type="text" id="firstName" name="firstName" ng-model="person.firstName" ng-minlength="2" /> <div ng-show="exampleForm.firstName.$dirty && exampleForm.firstName.$error.minlength"> First Name must be at least 2 characters long. </div> </div> </div>
What’s the problem then?
Well I’m very grateful that Angular is so flexible with it’s form validation but unfortunately it can end up resulting in a lot of typing and code to maintain in more complicated situations. This is completely understandable because Angular.js is a framework and not an all inclusive solution to all web problems that exist. (It happens to be a damn good solution to most web problems though once you roll your sleeves up though!)
Consider the field/form now has these pretty standard requirements:
- A message should display if either of these are invalid:
- First Name is required.
- First Name must be at least 2 characters long.
- If the user tries to submit an invalid form and the server call should NOT occur and validation messages should show up
If a user has cursor focus on a field and leaves it validation messages should show up.(way too hard to do for this simple example)
Now that sounds a little more complicated… but to be honest pretty much the form validation user story that most places I have worked want.
Unfortunately the simple code above gets a little nastier to implement this just in the HTML (and I didn’t even implement the focus validation either!)
<div ng-controller="exampleController" ng-form="exampleForm"> <label for="firstName">First Name *</label> <div> <input type="text" id="firstName" name="firstName" required="" ng-minlength="2" ng-maxlength="30" ng-model="person.firstName" /> <div class="validation-error" ng-show="(exampleForm.firstName.$dirty || invalidSubmitAttempt) && exampleForm.firstName.$error.required"> First Name is required. </div> <div class="validation-error" ng-show="exampleForm.firstName.$dirty && exampleForm.firstName.$error.maxlength || exampleForm.firstName.$error.minlength"> First Name must be between 2 and 30 characters. </div> </div> <button ng-click="save(exampleForm)">Save</button> </div>
And the associated controller JavaScript file:
angular.module('exampleModule', []) .controller('exampleController', function($scope) { $scope.save = function(ngForm) { if(ngForm.$invalid) { $scope.invalidSubmitAttempt = true; return; } alert('person saved!') } })
And a demo of it in action:
Woah! That’s quite a bit of work to get pretty basic form validation wouldn’t you agree? Also imagine having that ‘boiler plate’ code on every page across an entire application. Ick!
Another way: AngularAgility Form Extensions
If you remember Morpheus in the first Matrix movie he isn’t messing around. Neither is AngularAgility Form Extensions. Let’s take a look:
For just this HTML:
<div ng-controller="exampleController" ng-form="exampleForm"> <div> <input type="text" required="" ng-minlength="2" ng-maxlength="30" aa-field="firstName" /> </div> <button aa-save-form="save()">Save</button> </div>
And this JavaScript:
angular.module('exampleModule', ['aa.formExtensions']) .controller('exampleController', function($scope) { $scope.save = function() { //this WON'T get called unless the form is valid //aa-save-form only invokes the function if the //containing form is valid alert('person saved!'); }; });
You can get the same thing (also with field focus tracking!):
Wow! Thats a lot less code!
To recap the above using the “aa-field” directive in just a 1 line of HTML does the following for the First Name field:
- A message should display if either of these are invalid:
- First Name is required.
- First Name must be at least 2 characters long.
- If the user tries to submit an invalid form and the server call should NOT occur and validation messages should show up
- If a user has keyboard cursor focus on a field and leaves it validation messages should show up.
It also automatically generates the label for you “First Name *” based on the field that it is bound to. (* because it is required)
Additions to the Form object model
Form Extensions understands that you might want to code against this stuff your self so it also exposes a rich object model tacked on to what Angular.js already provides. Lets take a look…
- $aaFormExtensions is tacked onto the existing form object
- $invalidAttempt: If one was made (fields were invalid and aa-save-form was called) this is marked as true so messages show up
- firstName: Each field that is participating in form validation shows up as an object
- The $element for that field (for future extensions… stay tuned!)
- Each of the $errorMessages that currently apply to the field. These are automatically generated and change as input is added/removed.
- $hadFocus: if the field had focus and is invalid this will cause an error to appear automatically
But doesn’t all this auto-generation mean it’s not flexible?
Absolutely not. Form Extensions is designed to be completely customizable. “aa-field” is actually a composition of many directives that are all configurable with a rich defaultable and overriable provider model.
All these directives can be used separately and interchangably and are overriable, defaultable and configurable with providers
Let’s take a look at what calling “aa-field” actually generates:
<label for="dfb0191e-549e-4ae3-897d-de5a635b2329">First Name *</label> <input type="text" required="" ng-minlength="2" ng-maxlength="30" ng-model="firstName" name="firstName" aa-label="First Name" aa-val-msg="" class="aa-invalid-attempt aa-had-focus ..." id="dfb0191e-549e-4ae3-897d-de5a635b2329"> <!-- ngRepeat: msg in errorMessages --> <div class="validation-error ng-scope ng-binding" ng-show="showMessages" ng-repeat="msg in errorMessages" aa-val-msg-for="exampleForm.firstName"> First Name must be at least 2 character(s). </div> <!-- end ngRepeat: msg in errorMessages -->
Let’s explain what all this means and how customizable it is:
Label
- Was automatically generated because the textbox below had an “aa-label” on it.
- The label was generated/placed using the defaultLabelStrategy which is customizable and overridable both globally and individually
- Due to defaultLabelStrategy’s settings:
- The “aa-label” contents were generated by de-camel-casing the bound ng-model after the last ‘.’ (if any)
- Since the associated field is required it has a *
- The “for” was generated automatically since the textbox didn’t have an ID. If it did it would have used it
Textbox
- ng-model was generated from EXACTLY what was passed to aa-field
- name is always barBaz if aa-field=”foo.barBaz”
- aa-label’s contents are generated as explained above in “Label”
- aa-val-msg creates the validation message block below and automatically generates all the messages based the the applied validation rules
- It uses the defaultValMsgPlacementStrategy which is overridable on a per instance or global basis
- class Form Extensions (just like angular) adds additional classes to the element if you want to code you own CSS rules
- id was automatically generated so that a proper label could be associated
Validation Messages and Placement
- The defaultValMsgPlacementStrategy places validation messages directly below the field
- The placement can be wherever you want by adding a new strategy or using JUST the aa-val-msg-for=”exampleForm.firstName” directive directly
- Validation messages show up automatically as needed
In Conclusion
I hope you find AngularAgility Form Extensions useful. I know it has already saved me ALOT of time on my professional projects. And lets be honest coding out validation messages and labels isn’t the most fun thing to do in the first place. Stay tuned, much more productivity enhancing extensions coming to AngularAgility in the near future! Feel free to give me a pull if you think of any improvements or let me know if you have any questions. Enjoy!
Showing validation error below the input field using ng-show is really bad (but common) practice. This will make the whole formular move and jump whenever there is an error and whenever one is fixed.
Hi Martin,
Good feedback on that. I’ve almost always seen it done this way but I can see that it maybe visually isn’t the best. The awesome part about AngularAgility Form Extensions is that you can completely customize how and where the validation messages are placed (including whatever might be better UX). Please feel free to contribute a new message placement strategy to the repo so the world can use it!
John this is great! I have spent a ton of time creating form validations. I am defiantly going to take this for a spin and refactor some exiting forms I have. Great work!
To Martin’s comment: Perhaps there could be an option to output the validation message in an inline span as opposed to a div.
Awesome! Glad you like what you see so far. I’m using right now on a project and it really is making things go very fast and smoothly. Please let me know any additional feedback you have while you are using it and don’t hesitate to send me any pull requests you might have!
So what about custom validation? What if I want to make a custom directive how do I wire this up to work with this? Let’s say a unique check on a field? Great work, just would like an example of creating some custom validation.
Alright I figured this out. My issue was not adding a key to ngModel.$error to begin with, then I had to add the validationMessages in the aaFormExtensions provider.
var uniqueDirective = angular.module(‘uniqueDirective’, []);
uniqueDirective.directive(‘bcUnique’, [‘$http’, function ($http) {
return {
restrict: ‘A’,
require: [‘?ngModel’],
link: function (scope, element, attrs, controllers, ngModel) {
controllers[0].$error[‘unique’] = true;
element.on(‘blur’, function () {
scope.$apply(function () {
var httpConfig = { method: ‘GET’, url: ‘api/username/?username=’ + element.val() };
$http(httpConfig)
.success(function (data, status, headers, config) {
controllers[0].$setValidity(‘unique’, data);
});
});
});
}
}
}]);
.provider(‘aaFormExtensions’, function () {
…
this.validationMessages = {
required: “{0} is required.”,
email: “The field {0} must be an email.”,
minlength: “{0} must be at least {1} character(s).”,
maxlength: “{0} must be less than {1} characters.”,
min: “{0} must be at least {1}.”,
max: “{0} must be at most {1}.”,
pattern: “{0} is invalid.”,
url: “{0} must be a valid URL.”,
number: “{0} must be number.”,
unique: “{0} is not unique.”
};
…
}
Awesome work! I think I might try to add something like that baked in. I think it might not be a bad plan to have some more stuff in there than what is included with Angular by default. Also check out aa-auto-form-group too I just added it (one line form groups pretty sweet). I will be creating official API docs here soon.
Hi John, Wonderful work!
How could I find any example using angularAgility.NET library?
My application stack is as follow:
On the server side: SQL SERVER->EF->WEB API->ASP .NET MVC (razor templates)
On the client side: HTML5/CSS3->AngularJS
I’m looking for a better and easy way to rehuse validations from the server on the client side.
Thanks in advance.
Hi Rafael,
You could do something like this with AngularAgility:
https://github.com/AngularAgility/AngularAgility#or-configure-it-from-your-code
There aren’t any examples of the AngularAgility.NET library (someone else wrote it). Let me know what you come up with!
Very interesting article, I agree with everything you point in that defining error messages in the HTML is mixing markup and logic. I took a different approach to solving this problem by using an ngModel decorator and dynamic validation message and I’ve created my own validation module that does thing in the same sort of way
Sweet, and your website looks ver nice too! Yeah I’ll be interested to see where yours goes or perhaps we should join forces? I wonder if we both have the same features? When I made this 5 months ago or so I was quite surprised nothing like it already existed since it seemed like a no-brainer to me coming from ASP.NET MVC. I looked into decorators too and found them to be a little more confusing than they were worth for me since Angular also fully supports defining multiple directives with the same name (and different priorities even to boot). Nice plugin and website!
Hi John,
Thanks, yes I think joining forces might be the way to go. I have a few more feature ideas for the module like using angular translate to do a custom error message resolver to handle il8n with validation messages etc….
Give me an email and we can chat about this.
Like the site by the way some really interesting articles.
Whenever a try to set a bootstrap datepicker on a field that has Angular Agility directives it just doesn’t show up when I click on the field (the datepicker). Let’s say I have this textbox:
If I remove aa-label and aa-field and instead write ng-model=”newPlayer.BirthDate” it’s all working again. What’s up with that?
It looks like the html was erased from my previous post. However I meant just an ordinary input text with aa-label and aa-field on it.
Hey Rosen,
Sorry for the delayed response. I haven’t used AA with all types of directives out there but the beautify of it is that its completely optional (use it where it works type mentality). Feel free to respond with a plunker of your exact issue and maybe I can make an update to the framework to make it work better with certain types of directives otherwise feel free to not use it with the bootstrap date picker as well if you are having issues.
Hi John, Thanks for the awesome library love it.
I have one questions. How do I remove the label that gets generated?
Hey Josh,
Take a look at the demo page: http://angularagility.herokuapp.com/#/formExtensions/formExtensions/basic
I have many different examples of the various ways you can use AA. You don’t have to use aa-field-group or aa-field if you don’t want the label to be generated. For example you could do something like how Gender is working on the example page just using aa-val-msg to JUST get the validation message. Hope this helps… let me know if you find something you can’t do. Thanks!
My company doesn’t want the messages to dynamically appear/disappear at all. We originally developed a form with Angular validation a while back, but now we are being audited for usability. The screen reader needs to have focus on the input in order to read out the validation message (field and message could be connected using aria-describedBy) but the message only appears when focus moves OFF the input field, and as soon as focus moved to the input field… the message disappears!
Now the requirements I have been given are to display messages in a div at the top (with tabindex -1) and to set focus to it after submit. I’m currently looking at the best way to do this. It appears I need to unpick the automatic Angular.js validation messages and get rid of them (I’m sure this isn’t really necessary). I’d love a demo showing how to get the validation messages to appear in an error div at the top of the screen after the submit button is pressed!
Hi John,
I am working on a project that needs to display custom error messages from database. I was wondering how to dynamically set new error messages from server/db to “this.validationMessages”? I am unsure if Formconfig can be used to set dynamic messages on this.validationMessages
I did see intertech.com‘s example, but it does not set a error message from database
Appreciate your help.
Great work on this project. Keep it up !!!