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> 1 2 3 4 5 6 7 8 9 <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> 1 2 3 4 5 6 7 8 9 10 11 12 <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> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <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!') } }) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 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> 1 2 3 4 5 6 7 8 9 <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!'); }; }); 1 2 3 4 5 6 7 8 9 10 11 12 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 --> 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <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

was generated from EXACTLY what was passed to aa-field name is always barBaz if aa-field=”foo.barBaz”

is always barBaz if aa-field=”foo.barBaz” aa-label’s contents are generated as explained above in “Label”

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

creates the validation message block below and automatically generates all the messages based the the applied validation rules class Form Extensions (just like angular) adds additional classes to the element if you want to code you own CSS rules

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!