Angular-auto-validate

An automatic validation module for AngularJS which gets rid of excess html in favour of dynamic element modification to notify the user of validation errors

View project onGitHub

Demo.

The below demo shows the functionality of this module. Start typing into the inout boxes to see the validation.

Getting Started.

For the best overview of the module and module architecture see my original blog post here.

Install and manage angular-auto-validate with Bower.

$ bower install angular-auto-validate --save

Load the required javascript libraries

<script src="bower_components/angular-auto-validate/dist/jcs-auto-validate.min.js"></script>

Inject the jcs-auto-validate into your application

angular.module('my-app', ['jcs-autoValidate']);

If you are using bootstrap 3 that's it, unless you wish the valid and invalid states of your controls to be indicated via icons, otherwise auto-validate is setup!

angular.module('my-app')
        .run([
        'bootstrap3ElementModifier',
        function (bootstrap3ElementModifier) {
              bootstrap3ElementModifier.enableValidationStateIcons(true);
       }]);

if you are using Foundation 5 you will need to set the Foundation 5 element modifier as the default one

angular.module('my-app')
       .run([
       'validator',
       'foundation5ElementModifier',
       function (validator, foundation5ElementModifier) {
           validator.setDefaultElementModifier(foundation5ElementModifier.key);
       }]);

If you are not using Bootstrap3 or Foundation5 you will need to create a custom element modifier so see the below section

Your Form

Now that you are using auto-validate you haven't got to worry about putting your error messages in you html or using any angular expressions to show or hide them depending on the validation state. So now your form markup remains clean and maintainable

<form name="signupFrm" novalidate="novalidate">
    <div class="form-row">
        <label>Username:</label>
        <input name="username"
            ng-model="model.username"
            ng-minlength="3"
            ng-maxlength="10"
            required/>
    </div>
    <div class="form-row">
        <label>Password:</label>
        <input name="password"
            type="password"
            ng-model="model.password"
            ng-minlength="3"
            ng-maxlength="10"
            required/>
    </div>
</form>

Form Resetting

Auto-Validate automatically hooks into your form reset and clears all the validation error states from the input controls. To enable this simply add a button of type reset within your form element (just like you would a submit button). Using the $setPristine method on the ngModel controller will also return the element to its default visual state and clear and validation errors or success styling.

<button type="reset">Reset</button>

Enable / Disable Visual Styling

If you want to globally disable visual styling of elements you can call the below methods on the validator provider. You might for instance not want valid elements styled. By default elements will be styled in all validation states.

angular.module('my-app')
            .run([
                'validator',
                function (validator) {
                    validator.setValidElementStyling(false);
                    validator.setInvalidElementStyling(false);
            }]);

You can disable visual validation states at an element level as well by adding the attributes disable-valid-styling="true" to disable the valid state element styling and disable-invalid-styling="true" to disable the invalid state visual styling.

<!-- auto-validate will not style the element when it is in a valid state -->
            <input type="text"
                ng-model="model.firstname"
                required
                disable-valid-styling="true"/>

            <!-- auto-validate will not style the element when it is in an invalid state -->
            <input type="text"
                ng-model="model.surname"
                ng-minlength="2"
                disable-invalid-styling="true"/>
            


Disable Validation Messages

You can disable the validation message appearing for a particular element by adding the attribute disable-validation-message="" to the element.

<!-- auto-validate will not add the error message to the element when it is in an invalid state -->
                <input type="text"
                ng-model="model.surname"
                ng-minlength="2"
                disable-validation-message=""/>

Overriding Error Messages

By default this module ships with error messages for all the supported validation types in angular. However, if any default error message is not quite what you want for a particular element you can override the errorType that is used for a particular validation type by adding the attribute *-err-type="errorTypeKey" where '*' is the validation type you want to override to the specific element you want changed.

<!-- auto-validate will try and find a error message with a key of 'firstNameRequired' -->
<input type="text"
    ng-model="model.firstname"
    required
    required-err-type="firstNameRequired"/>

<!-- auto-validate will try and find a error message with a key of 'surnameMinLength' -->
<input type="text"
    ng-model="model.surname"
    ng-minlength="2"
    ng-minlength-err-type="surnameMinLength"/>

Changing Element States

If you want a certain element to appear differently to other elemenets when it is valid/invalid you can still do that by just specifying which element modifier should be used to modify the elements visual state. This can be achieve by using the attribute element-modifier="elementModifierKey".

<!-- auto-validate will use the default element modifier -->
    <input type="text"
    ng-model="model.firstname"
    required
    ng-minlength="2" />

    <!-- auto-validate will try to find a custom modifier with the key 'myCustomModifier' -->
    <input type="text"
    ng-model="model.middlename"
    required
    ng-minlength="2"
    element-modifier="myCustomModifier" />

    <!-- auto-validate will try to find a custom modifier with the key 'myOtherCustomModifier' -->
    <input type="text"
    ng-model="model.surname"
    required
    ng-minlength="2"
    element-modifier="myOtherCustomModifier" />

NgModelOptions Support

By default AngularJS triggers validation on change, while this is great for illustrating the power of Angular it can be somewhat annoying to the user if they are typing their name in and after the first letter it tells them their name must be more than 3 characters. Now Angular 1.3+ has the support for ngModelOptions which lets us specify when Angular should update the bound model and fire the validation process. We can now easily specify it should be on blur rather than on change. However, those of us who need to support older browsers (IE8) cannot use this newer version as it does not officially support them. So, I have added very rudimentary support for the updateOn option of ngModelOptions even for Angular 1.2 and below! Simply add the ngModelOption directive as usual and you can easily change the trigger event of the element.


<input type="text"
       ng-model="model.name"
       ng-model-options="{updateOn: 'blur'}"
       required
       ng-minlength="5" />
            

ngSubmit

The typical way to test if a form is valid before submitting is to pass the form into the ngSubmit method you define on your scope. This isn't ideal as you are passing in a psuedo UI element into your controller. I would prefer that the ngSubmit function you defined is simply not called if the form is invalid. Luckily, this is exactly what happens in auto-validate. When the user clicks on the submit button the form is validated and the child UI elements have their validation states applied so the user can see what is valid/invalid. Only if the whole form is valid will the ngSubmit method be called. Of course, it you would prefer it is called even if the form is invalid you can use the ngSubmitForce attribute shown below.

<form name="signupFrm" novalidate="novalidate" ng-submit="submit();" ng-submit-force="true">
    <div class="form-row">
    <label>Username:</label>
    <input name="username"
        ng-model="model.username"
        ng-minlength="3"
        ng-maxlength="10"
        required/>
    </div>
    <div class="form-row">
    <label>Password:</label>
    <input name="password"
        type="password"
        ng-model="model.username"
        ng-minlength="3"
        ng-maxlength="10"
        required/>
    </div>
    </form>

Error Message Resolver

An error message resolver is an object that turns a validation error type (required, minlength etc) into an actual validation message that can be displayed to the user to help them rectify the validation error. By default auto-validate has one built in that contains English error messages for all the default angular validation types. However if you have custom validation filters you will need to add them to the default error message resolver in order to provide a custom error message when that validation type if triggered.

angular.module('jcs-autoValidate')
    .run([
    'defaultErrorMessageResolver',
    function (defaultErrorMessageResolver) {
        // passing a culture into getErrorMessages('fr-fr') will get the culture specific messages
        // otherwise the current default culture is returned.
        defaultErrorMessageResolver.getErrorMessages().then(function (errorMessages) {
          errorMessages['myCustomError'] = 'My custom error message';
          errorMessages['anotherErrorMessage'] = 'An error message with the attribute value {0}';
        });
    }
]);
            

i18n

The default error message resolver that is enabled out of the box now has i18n support! While this is fairly basic support it gives enough to not have to implement a custom error message resolver to do complex translations. The default culture is 'en-GB' which is basically 'en-US'.
Setting a Culture
The default culture for error messages is 'en-GB' this can be changed by simply setting the correct culture on the defaultErrorMessageResolver. This will automatically go off and fetch the correct culture resource file from the server. By default the library will request the file from '{js/angular-auto-validate/dist/lang}/jcs-auto-validate_##-##.json'. However, if the culture files are in a different location you can set the root path for the culture files (the part of the above url in brackets {js/angular-auto-validate/dist/lang}) by calling setI18nFileRootPath('some/path)
angular.module('jcs-autoValidate')
.run([
    'defaultErrorMessageResolver',
    function (defaultErrorMessageResolver) {
        // To change the root resource file path
        //defaultErrorMessageResolver.setI18nFileRootPath('some/path);
        defaultErrorMessageResolver.setCulture('fr-FR');
    }
]);
            
Alternatively, you can provide your own loading function to the setCulture method that should return a promise and resolve that promise with the object structure expected in a resource file.
angular.module('jcs-autoValidate')
.run([
    '$q',
    'defaultErrorMessageResolver',
    function ($q, defaultErrorMessageResolver) {

        defaultErrorMessageResolver.setCulture('fr-FR', function () {
            var defer = $q.defer();

            // you can get the error messages however you want and resolve the promise when they are loaded
            // defer.resolve(errorMessagesRetreivedBySomeMethod);
            return defer.promise;
        });
    }
]);
            
Small Caveat

While I might know lots of programming languages I do not know many (well only one) actual language. So at the moment we only have English and French translations. If you can translate the default 'angular-auto-validate_en-GB.json' into any other language that would be great! PR the repo or email me it as that would be awesome!


Custom Error Message Resolver

If you need more fined grained control over how a validation error type is converted into an error message you may wish to create a custom error message resolver. This is really simple and you can do anything you like to resolve an error type into a error message. You might for instance need to provide a custom translation into the user's language or go to the server etc.


angular.module('my-app')
    .factory('myCustomErrorMessageResolver', [
    '$q',
    function ($q) {
        /**
        * @ngdoc function
        * @name defaultErrorMessageResolver#resolve
        * @methodOf defaultErrorMessageResolver
        *
        * @description
        * Resolves a validate error type into a user validation error message
        *
        * @param {String} errorType - The type of validation error that has occurred.
        * @param {Element} el - The input element that is the source of the validation error.
        * @returns {Promise} A promise that is resolved when the validation message has been produced.
        */
        var resolve = function (errorType, el) {
            var defer = $q.defer();
            // do something to get the error message
            // then resolve the promise defer.resolve('some error message');
            return defer.promise;
        };

        return {
            resolve: resolve
        };
    }
]);

// now set the custom error resolver as the module's default one.
angular.module('my-app')
    .run([
    'validator',
    'myCustomErrorMessageResolver',
    function (validator, myCustomErrorMessageResolver) {
        validator.setErrorMessageResolver(myCustomErrorMessageResolver.resolve);
    }
]);

Custom Element Modifier

If you are not using Bootstrap3 or Foundation5 CSS libraries you will need to create a custom element modifier which takes care of updating the visual state of an element, don't worry this is really easy! You can see a few example of what to put in them here and here.

angular.module('my-app')
    .factory('myCustomElementModifier', [
        function () {
            var /**
            * @ngdoc function
            * @name myCustomElementModifier#makeValid
            * @methodOf myCustomElementModifier
            *
            * @description
            * Makes an element appear valid by apply custom styles and child elements.
            *
            * @param {Element} el - The input control element that is the target of the validation.
            */
            makeValid = function (el) {
            // do some code here...
            },

            /**
            * @ngdoc function
            * @name myCustomElementModifier#makeInvalid
            * @methodOf myCustomElementModifier
            *
            * @description
            * Makes an element appear invalid by apply custom styles and child elements.
            *
            * @param {Element} el - The input control element that is the target of the validation.
            * @param {String} errorMsg - The validation error message to display to the user.
            */
            makeInvalid = function (el, errorMsg) {
            // do some code here...
            },


            /**
            * @ngdoc function
            * @name myCustomElementModifier#makeDefault
            * @methodOf myCustomElementModifier
            *
            * @description
            * Makes an element appear in its default visual state.
            *
            * @param {Element} el - The input control element that is the target of the validation.
            */
            makeDefault = function (el) {
                // return the element to a default visual state i.e. before any form of validation was applied
            };

            return {
                makeValid: makeValid,
                makeInvalid: makeInvalid,
                makeDefault: makeDefault,
                key: 'myCustomModifierKey'
            };
        }
    ]);

// now register the custom element modifier with the auto-validate module and set it as the default one for all elements
angular.module('my-app')
    .run([
    'validator',
    'myCustomElementModifier',
    function (validator, myCustomElementModifier) {
        validator.registerDomModifier(myCustomElementModifier.key, myCustomElementModifier);
        validator.setDefaultElementModifier(myCustomElementModifier.key);
    }
]);

Authors and Contributors

Originally authored by Jon Samwell.

Contributions from @DanielBodnar, @jgoux, @vonwolf & @miseeger

Support or Contact

Add all issues and feature ideas to the github repo