Coding javaScript

Input validation in Angular

Validation is one of the main issues that developers have to deal with when handling user input in forms. The premise is that all user data coming in may be corrupt and some users may have bad intentions so validate inputs on the client and on the server side.
Maybe for this reason, angular (1.3+) provides a pretty powerful validation api through the all famous ngModel directive and its controller. By adding our custom directive to the input and requiring ngModel we can have a grip over the ngModel Controller which provides the validation api. So we start with something like:

disclaimer- should be only start-date in the markup. My syntax highlighter adds the =”” so ignore those.

So far so good- we have a form with an input where we will require the user to type in a date in a specific format within a specified date range and we will validate if that date is available for reservation on the server. So we bind te input to reservationStartDate and put in our custom directive – startDate.

Next thing, lets create the directive itself:

.directive('startDate',function(constVars,dateParserService,$http){ 
	return {
        restrict: 'A',
        require: ['ngModel'],
        link: link
    };

    function link(scope, element, attr, ctrls) {
        var ngModelController = ctrls[0];
    }

})

So far we don’t do anything but the important takeaway here is the require: [‘ngModel’] line when declaring our directive which says – “Hey let me use the ngModel Controller here”. Assume that in constVars we hold a date regex and dateParserService is custom logic that we created for parsing dates from strings. All the validation will happen in the link function of our directive and the required ngModel Controller is passed in the link function through the fourth argument (ctrls). In this case we will use the following methods for validation:

$parsers – that is a collection of functions where we sanitize input from user before handing it to the $validators. Every parser function passes the sanitized value to the next one. If we ever return undefined, the pipeline doesn’t reach the $validators.

$validators – name is pretty exemplary here – these are collection of functions where we apply validation logic.

$asyncValidators – same as above but we typically use these for server, api or whatever long running operations validation. They start not one after the other but simultaneously and deal with promises rather than values. If all resolve the value than validation passes, if one rejects it – validation fails. Let’s get to some specific example.

First of all, we want to check if the manually input value is in the format that we want the user to use and we will use regex – so we will put that in the $parsers:

function link(scope, element, attr, ctrls) {
        ngModelController.$parsers.unshift(function (viewValue) {
                var date;
                if (angular.isString(viewValue)) {
                    if (!constVars.dateFormatRegex.test(viewValue)) return undefined;
                    else {
                        date = dateParserService.parse(viewValue, "dd-MM-yyyy");
                        if (isNaN(date)) {
                            return undefined;
                        }else{
		            return date;
			}
                    }
                }
        });
}

I we can parse it we pass the parsed Date object down the pipeline, if not – return undefined, which stops the value propagation down the pipeline and sets myForm.reservationStartDate.$error.parse = true.
If we parsed the user input to date we want to validate if the user entered a date in a correct date interval (we don’t want dates before today for a reservation). We will put that into $validators:

		ngModelController.$validators.afterToday=function (modelValue,viewValue) {
			var today=new Date();
			if(today < modelValue){
				return true;
			}
			return false;	
        };

So after the date has been parsed it will be passed to the afterToday for business rule validation. Let's go a little further and add an asyncValidator. We will validate the date for availabiliy on the server:

ngModelController.$asyncValidators.isDateAvailable=function (modelValue) {
    return $http.post('api/date/available',modelValue).then(function(success){                       
        //yeah, went through                      
    },function(error){
        // no availability
    })
};

As i said, async validators (you may have many of those) will start running simultaneously and input will be validated only if all resolve the promise. You may give the user indication that something is loading by:

Checking Value on the server....

So that is the main validation pipeline of the ngModelController - $parsers-> $validators & $asyncValidators. There is one more hook that you can use - $viewChangeListeners which is again array of functions. It does not take args or return anything - these functions are used usually for additional $watches on the model instead of some validation logic. What is provided more in the api are methods to change the input state as $setPristine(),$setDirty(), $setTouched(), $setUntouched(). Those are self-explanatory so i wont elaborate on them.

By default the view value gets passed down the validation pipeline on every change (ie keystroke) which might be not necessary. You may want to validate the input on blur event only. So that is very easily configurable - you only need to decorate your input markup with ng-model-option:{updateOn:'blur'}

You can clone the source for this example and some more on https://github.com/ivopashov/angular-presentation It is in app->ngModelExample

Leave a Reply

Your email address will not be published. Required fields are marked *