Integrating client and server side validation (Part 2)

Home / Integrating client and server side validation (Part 2)

Continuing the discussion of integrating server-side validation with client-side validation, let’s check out a demo of this in action.

All of the Angular code for this demo has been moved to my general demo Github repository.


In part one of this series, I showed most of the pieces involved in connecting the dots between server and client side validation. They include a validationService to process the response from the server, a directive to handle key/click events on the form elements to clear any existing server errors, and a simple form/controller to glue these pieces together. The one piece that I had to put together was a simple server-side application to actually return errors. I chose to write this in Node using Express.

If I haven’t stated enough why one would want to integrate server-side validation with the client’s validation, it’s because the server should never trust external input. Performing only client-side validation is not real validation of the data. Anyone could completely bypass your validation, for example, by making a PUT/POST with something like a REST client. In that regard, client-side-only validation is only a nicety to help prevent the user from unnecessarily submitting data when there are obvious errors/omissions. Also, client-side validation cannot always perform the same validation that the server is capable of. In the event that the server detects a property validation issue and/or scenario after a PUT/POST, we need a way to convey this exception back to the user. That’s what this demo is all about.

The server-side code is pretty simple. As I explained in the previous post, the server needs to return a 400 error, a statusText of ‘validationException’, and a dictionary of errors. The server also has to provide CORS access since it has to be accessible over XHR from whatever sandbox JavaScript hosting I’m using for the demo Angular applications. You can see the full source for this on my azure-website github repository. The basic gist of the website, though, is to provide a single API POST method to process a form submission from Angular’s $http.post() method. That code looks like this:

router.post('/user', function (req, res, next) {
    var randNumber = function() {
        var min = 1;
        var max = 100;
        var random = Math.floor(Math.random() * (max - min + 1)) + min;
        return random;
    };
    
    var firstname = req.body.firstname;
    var lastname = req.body.lastname;
    var email = req.body.email;
    
    var error = [
        { Key: 'firstname', Value: ['Your first name is no good'] },
        { Key: 'lastname', Value: ['Last name is no good'] },
        { Key: 'email', Value: ['Email is invalid'] },
    ];
    
    // Randomly choose which errors to keep
    for (var i = error.length - 1; i >= 0; i--) {
        var rand = randNumber();
        if (rand > 50) {
            error.splice(i, 1);
        }
    }
    
    if (error.length > 0) {
        var errorStr = JSON.stringify(error);
        res.writeHead(400, 'validationException', { 'content-type': 'text/plain' });
        res.end(errorStr);
    } else {
        res.end();
    }
});

Essentially, I’m expecting (3) form inputs to be submitted and I randomly choose which to indicate are in error. Imagine that in a real application, this error determination would be whatever mechanism you’re validation form submissions against.

As I indicated previously, the validationService will take this response and determine which form elements, if any, need to be marked as invalid. I probably showed that code in the previous post, but it’s just iterating over the dictionary returned, finding the appropriate keyed element in the form controller, and set the validity state:

var processServerErrors = function (formCtrl, errors) {
	if (errors && errors.length > 0) {
		for (var i = 0; i < errors.length; i++) {
			var key = errors[i].Key;
			var name = key.split('.').slice(-1);
			for (var j = 0; j < errors[i].Value.length; j++) {
				var formElement = formCtrl[name];
				if (formElement) {
					var errorMessage = errors[i].Value[j];
					formElement.$setValidity('server', false);
					formElement.$error.serverMessage = errorMessage;
				}
			}
		}
	}
};

The HTML template is nothing special, really. It only has elements to display the server errors and attaches our serverError directive so that the user clears any serverError when they interact with a form element either through clicking it or typing into it.

<script type="text/ng-template" id="state1.html">
	<form class="form" name="vm.form" role="form" novalidate>
		<div class="form-group">
			<div class="form-group" ng-class="{ 'has-error': vm.form.firstname.$invalid && vm.isSubmitted }">
				<label for="firstname">First Name</label>
				<div class="form-group" ng-class="{ 'has-error': vm.form.firstname.$invalid && vm.isSubmitted }">
					<input id="firstname" name="firstname" type="text" class="input primary form-control form-control-inline" placeholder="First Name"
					ng-model="vm.firstname" ng-required="true" autocomplete="off" server-error="true" />
					<small id="firstnameReq" class="error" ng-show="vm.isSubmitted && vm.form.firstname.$error.required">This is a required field.</small>
					<small class="error" ng-show="vm.isSubmitted && !vm.form.firstname.$error.required && vm.form.firstname.$error.server">
						{{vm.form.firstname.$error.serverMessage}}
					</small>
				</div>
			</div>
			<div class="form-group" ng-class="{ 'has-error': vm.form.lastname.$invalid && vm.isSubmitted }">
				<label for="lastname">Last Name</label>
				<div class="form-group" ng-class="{ 'has-error': vm.form.lastname.$invalid && vm.isSubmitted }">
					<input id="lastname" name="lastname" type="text" class="input primary form-control form-control-inline" placeholder="Last Name"
					ng-model="vm.lastname" ng-required="true" autocomplete="off" server-error="true" />
					<small id="lastnameReq" class="error" ng-show="vm.isSubmitted && vm.form.lastname.$error.required">This is a required field.</small>
					<small class="error" ng-show="vm.isSubmitted && !vm.form.lastname.$error.required && vm.form.lastname.$error.server">
						{{vm.form.lastname.$error.serverMessage}}
					</small>
				</div>
			</div>
			<div class="form-group" ng-class="{ 'has-error': vm.form.email.$invalid && vm.isSubmitted }">
				<label for="email">Email</label>
				<div class="form-group" ng-class="{ 'has-error': vm.form.email.$invalid && vm.isSubmitted }">
					<input id="email" name="email" type="text" class="input primary form-control form-control-inline" placeholder="Email"
					ng-model="vm.email" ng-required="true" autocomplete="off" server-error="true" />
					<small id="emailReq" class="error" ng-show="vm.isSubmitted && vm.form.email.$error.required">This is a required field.</small>
					<small class="error" ng-show="vm.isSubmitted && !vm.form.email.$error.required && vm.form.email.$error.server">
						{{vm.form.email.$error.serverMessage}}
					</small>
				</div>
			</div>
		</div>
		<div class="form-group buttons">
			<button id="clear" type="button" class="btn btn-default" ng-click="vm.discard()">Clear</button>
			<button id="save" type="button" class="btn btn-primary pull-right" ng-click="vm.submit()" ng-disabled="!vm.form.$dirty">Submit</button>
		</div>
	</form>
</script>

Finally, below is an interactive pen. Each of the form elements has a ng-required attribute. So, something has to be entered. Clicking submit performs the post to the server. Clicking “Clear” will discard/reset the form and clear any errors.

And, there’s a plunk if you prefer Plunker.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.