Wednesday, January 1, 2014

Validations in AngularJS

There are different types of validation that we can do on Angular.
1. A simple form that has multiple control
2. A basic form that does not have many control in it, and that may need inline validation

Let's take a look at some common validation patterns on the form, like minimum, maximum length, required fields. The sample below should make it self evident. Run the sample code with the validations set, you will see a nice pop-over on every field if the validation fails. A lot of the manual effort has been taken out. Good.

<fieldset>
	<legend>Make your order</legend>
	<table>
		<tr>
			<td>Item</td>
			<td><input type="text" name="item" required ng-minlength=5 ng-model="purchaseItem.item"></td>
		</tr>
		<tr>
			<td>Quantity</td>
			<td><input type="text" name="quantity" required ng-model="purchaseItem.quantity" ng-minlength=1></td>
			<td><select name="measure" required ng-model="purchaseItem.measure">
				<option ng-repeat="quantity in quantities"> {{ quantity }}</option>
			</select></td>
		</tr>
		<tr>
			<td>From store</td>
			<td><input type="text" name="store" required ng-model="purchaseItem.store"></td>
		</tr>
		<tr>
			<td>Description</td>
			<td><textarea name="description" ng-maxlength=50 ng-model="purchaseItem.description"></textarea></td>
		</tr>
		<tr>
			<td><button ng-click="orderNewItem(purchaseItem)">Add</button></td>
			<td><button type="reset()">Clear/Cancel</button></td>
		</tr>
	</table>
</fieldset>

Next up is form submission. Again, the conventional way of doing things is to have a ng-submit tag with the method that needs to be invoked on it. I prefer associating methods on the submit method. Just my way of doing things, nothing against the conventional approach. We will see both the ways. Sample below should help our case

<tr>
	<td><button ng-click="orderNewItem(purchaseItem)">Add</button></td>
	<td><button type="reset()">Clear/Cancel</button></td>
</tr>

For the submit button to be evaluated from the controller, we may have to corresponding submit method. Here's the sample on that

addOrderController.js

function addOrderController($scope) {
	$scope.heading="Place your order here";
	$scope.scope="addorder";
	$scope.quantities=['Packets', 'lbs', 'Kg', 'ml', 'pieces', 'none'];
	$scope.purchaseItem = {};

	$scope.orderNewItem = function(purchaseItem) {
		console.log('Item to purchase is :' + purchaseItem.item);
	};

	$scope.reset = function() {
		$scope.purchaseItem = {};
	};
}

Here's the search form that has the other way for validation
<form name="searchForm" novalidate ng-submit="searchItem()">
		<fieldset>
			<legend>Search for an order</legend>
			<table>
				<tr>
					<td>Item</td>
					<td><input type="text" name="item" ng-model="searchItem.item" ng-maxlength=20></td>
					<td>From store</td>
					<td><input type="text" name="store" ng-model="searchItem.store" ng-maxlength=20></td>
					<td><button type="submit" ng-disabled="searchForm.$invalid">Search</button></td>
				</tr>
			</table>
		</fieldset>
		<div ng-show="searchForm.item.$dirty && searchForm.item.$invalid">
			<p>ERROR</p>
			<small ng-show="searchForm.item.$error.maxlength">Item name you are searching for should not be more than 20 characters long </small>
		</div>
		<div ng-show="searchForm.item.$dirty && searchForm.store.$invalid">
			<p>ERROR</p>
			<small ng-show="searchForm.store.$error.maxlength">Store name should not be more than 20 characters long </small>
		</div>
	</form>

Most of the attributes on HTML directives will make sense, but for "novalidate" in form element
"novalidate" prevents the browser from submitting the form

But this is not all, you can do a lot more. If your control needs  custom validation like pattern-matching, here's how you can do it

<input type="text" ng-pattern="/a-zA-Z/" />

We are so far good with client-side validation. How do you we get server-side validation done?
You can always do a $http request and on success you can set the success flag. We will do that in the examples to come down the line.

One other way you will come across in form validation is 'or'. Let's say you are building a search form where you have 2 fields and one of the 2 fields should be filled in for the submit button to be enabled. You can do one of these

<form name="searchForm" novalidate ng-submit="searchItem()">
	<fieldset>
		<legend>Search for an order</legend>
		<table>
			<tr>
				<td>Item</td>
				<td><input type="text" name="item" ng-model="searchItem.item" ng-maxlength=20 ng-required="!searchItem.store" ></td>
				<td>From store</td>
				<td><input type="text" name="store" ng-model="searchItem.store" ng-maxlength=20 ng-required="!searchItem.item"></td>
				<td><button type="submit" ng-disabled="searchForm.$invalid">Search</button></td>
			</tr>
		</table>
	</fieldset>
	<div ng-show="searchForm.item.$dirty && searchForm.item.$invalid">
		<small ng-show="searchForm.item.$error.maxlength">Item name you are searching for should not be more than 20 characters long </small>
	</div>
	<div ng-show="searchForm.item.$dirty && searchForm.store.$invalid">
		<small ng-show="searchForm.store.$error.maxlength">Store name should not be more than 20 characters long </small>
	</div>
</form>

Now, let's say we have another control and you are not interested in adding a lot of div tags around to deal with different types of errors. All you want is, allow JS to handle majority of the stuff. I see 2 reasons you want to do it.
1. Compilcated validations based on business
2. Want to re-use existing JS code in the previous application and this way you are confident that nothing is broken

If you are not in the above 2, I strongly encourage you to go the Angular way for validation
Anyway, back to what we were talking about. I added another control to my search form that does not have any of 'ng-*' valiudations on it. Instead, when the user taps on the submit button, JS kicks in does the validation and sets the error state for the new control. Then the typical Angular will detect that the form is invalid due to the field and displays the error message. Hope that is to your liking

searchOrderController.js

function searchOrderController($scope) {
	$scope.heading = "Search For your orders";
	$scope.scope = "searchorder";
	$scope.searchItem = {};
	$scope.submitted = false;
	$scope.errors = null;
	$scope.searchItem = function() {
		console.log('Search for item: ' + $scope.searchItem.item);
		console.log('Search for item bought from: ' + $scope.searchItem.store);
		console.log('Item purchased by: ' + $scope.searchItem.purchasedBy);
		if($scope.searchItem.purchasedBy == null) {
			$scope.searchForm.purchasedBy.$setValidity("server", false);
			$scope.searchItem.errors = "Purchased by should nto be EMPTY";
		} else {
			$scope.submitted = true;
		}
		console.log('processed');
	};
}

searchorder.html

<form name="searchForm" novalidate ng-submit="searchItem()">
	<fieldset>
		<legend>Search for an order</legend>
		<table>
			<tr>
				<td>Item</td>
				<td><input type="text" name="item" ng-model="searchItem.item" ng-maxlength=20 ng-required="!searchItem.store" ></td>
				<td>From store</td>
				<td><input type="text" name="store" ng-model="searchItem.store" ng-maxlength=20 ng-required="!searchItem.item"></td>
				<td>Purchased By</td>
				<td><input type="text" name="purchasedBy" ng-model="searchItem.purchasedBy" ng-maxlength=20></td>
				<td><button type="submit" ng-disabled="searchForm.$invalid">Search</button></td>

			</tr>
		</table>
	</fieldset>
	<div ng-show="searchForm.item.$dirty && searchForm.item.$invalid">
		<small ng-show="searchForm.item.$error.maxlength">Item name you are searching for should not be more than 20 characters long </small>
	</div>
	<div ng-show="searchForm.item.$dirty && searchForm.store.$invalid">
		<small ng-show="searchForm.store.$error.maxlength">Store name should not be more than 20 characters long </small>
	</div>
	<div ng-show="searchForm.purchasedBy.$invalid">
		<small>{{ searchItem.errors }}</small>
	</div>
</form>
Hope this helps....in our next samples we have to come up with some sort of template for the application. We have a lot of redundant code out there.

No comments: