Skip to content

Instantly share code, notes, and snippets.

@jholland918
Created June 26, 2015 21:53
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jholland918/d3e74f894cc9ac782c7e to your computer and use it in GitHub Desktop.
Save jholland918/d3e74f894cc9ac782c7e to your computer and use it in GitHub Desktop.
AngularJS form validation example validating an interrelated collection of controls
// http://plnkr.co/edit/HoG0XH
(function () {
'use strict';
var app = angular.module('app', []);
})();
(function () {
'use strict';
angular
.module('app')
.controller('controller1', controller1);
controller1.$inject = ['$location'];
function controller1($location) {
var data = {
'title': 'More Snacks Please',
'description': 'Add beef jerky to the breakroom snacks.',
'reviewers': [{
'id': 1,
'name': 'John Smith',
'office': 'Branch',
'route': '1'
}, {
'id': 2,
'name': 'Amy Jones',
'office': 'Corporate',
'route': '2'
}, {
'id': 3,
'name': 'Lucy Laflamme',
'office': 'Corporate',
'route': '3'
}]
};
var vm = this;
vm.data = data;
vm.isValidReviewers = null;
vm.routeChanged = routeChanged;
vm.lastRouteChanged = null;
activate();
function activate() { }
function routeChanged(name) {
vm.lastRouteChanged = name;
}
}
})();
<!DOCTYPE html>
<html ng-app="app">
<head>
<script data-require="angular.js@1.3.0" data-semver="1.3.0" src="https://code.angularjs.org/1.3.0/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="app.js"></script>
<script src="controller1.js"></script>
<script src="reviewersValidator.js"></script>
<script src="routeOrderValidator.svc.js"></script>
<script src="uniqueRouteValidator.svc.js"></script>
<script src="isZeroValidator.js"></script>
</head>
<body ng-controller="controller1 as vm">
<h1>Request Form</h1>
<form name="form1">
<label>
Title:
<input type="text" name="title" ng-model="vm.data.title" />
</label>
<label>
Description:
<textarea type="text" name="description" ng-model="vm.data.description"></textarea>
</label>
<h3>Reviewers</h3>
<ul ng-repeat="reviewer in vm.data.reviewers">
<li>
<label>
Name:
<input type="text" name="name_{{$index}}" ng-model="reviewer.name" />
</label>
<label>
Office:
<select name="office_{{$index}}" ng-model="reviewer.office">
<option>Branch</option>
<option>Corporate</option>
</select>
</label>
<label>
Routing Order:
<input type="text" name="route_{{$index}}" ng-model="reviewer.route" ng-model-options="{ allowInvalid: true }" is-zero-validator />
<p ng-show="form1.route_{{$index}}.$error.uniqueRoute" class="error">Not a unique routing order!</p>
<p ng-show="form1.route_{{$index}}.$error.routeOrder" class="error">Branch employees must be first in the routing order!</p>
<p ng-show="form1.route_{{$index}}.$error.isZero" class="error">The test isZero validator is angry!</p>
</label>
</li>
</ul>
<div>
<div reviewers-validator name="reviewers" ng-model="vm.data.reviewers" ng-model-options="{ allowInvalid: true }" class="reviewers-validator">
<p ng-show="form1.reviewers.$error.uniqueRoute" class="error">Duplicate routing orders found.</p>
<p ng-show="form1.reviewers.$error.routeOrder" class="error">Invalid routing order, Branch employees must appear first in routing order.</p>
</div>
</div>
</form>
</body>
</html>
(function() {
'use strict';
angular
.module('app')
.directive('isZeroValidator', isZeroValidator);
function isZeroValidator() {
return {
require: 'ngModel',
link: link
};
function link($scope, element, attrs, ngModel) {
ngModel.$validators.isZero = function (value) {
return (value !== "0");
};
}
}
})();
(function () {
'use strict';
angular
.module('app')
.directive('reviewersValidator', reviewersValidator);
reviewersValidator.$inject = ['uniqueRouteValidator', 'routeOrderValidator'];
function reviewersValidator(uniqueRouteValidator, routeOrderValidator) {
// Usage:
// <div reviewers-validator
// name="reviewers"
// ng-model="vm.data.reviewers"
// ng-model-options="{ allowInvalid: true }" >
// <p ng-show="form1.reviewers.$error.uniqueRoute" class="error">Error text.</p>
// <p ng-show="form1.reviewers.$error.routeOrder" class="error">Error text.</p>
// </div>
//
// Note 1:
// Look at the allowInvalid option in ngModelOptions and consider if you want to
// set allowInvalid to true for this directive. Using allowInvalid: true is recommended.
// ngModelOptions documentation: https://docs.angularjs.org/api/ng/directive/ngModelOptions
//
// Note 2:
// You might also want to use 'ng-model-options="{ allowInvalid: true }"' on the form
// controls you wish to validate in this directive because they may have individual
// validators of their own that will set their model to undefined when invalid.
//
// Alternatively, you could validate against the form control's $viewValue data,
// which would require you to parse the FormController properties to extract the
// $viewValue data for each form control.
var directive = {
require: ['ngModel', '^form'],
link: link,
restrict: 'EA'
};
return directive;
function link(scope, element, attrs, controllers) {
var ngModel = controllers[0];
var form = controllers[1];
scope.$watch(attrs.ngModel, function () {
ngModel.$validate();
}, true);
ngModel.$validators.routeOrder = function () {
return routeOrderValidator.validate(form, scope.$eval(attrs.ngModel));
};
ngModel.$validators.uniqueRoute = function () {
return uniqueRouteValidator.validate(form, scope.$eval(attrs.ngModel));
};
}
}
})();
(function () {
'use strict';
angular
.module('app')
.service('routeOrderValidator', routeOrderValidator);
routeOrderValidator.$inject = ['$filter'];
function routeOrderValidator($filter) {
this.validate = validate;
var fieldNamePrefix = 'route_';
var validationErrorKey = 'routeOrder';
function validate(form, collection) {
if (!collection) {
return true;
}
var invalidItems = getInvalidItems(collection);
if (!invalidItems || invalidItems.length === 0) {
setValid(form);
return true;
}
setInvalid(form, invalidItems);
return false;
}
function getInvalidItems(reviewers) {
reviewers.forEach(function (reviewer, index) {
reviewer.originalIndex = index;
});
var sorted = $filter('orderBy')(reviewers, 'route');
var misordered = [];
var branchCount = 0;
sorted.forEach(function (reviewer, index) {
if (reviewer.office == 'Branch') {
if (index !== branchCount) {
misordered.push(reviewer.originalIndex);
}
branchCount++;
}
});
return misordered;
}
function setValid(form) {
var ctrls = getModelControllers(form);
ctrls.forEach(function (ctrl) {
ctrl.$setValidity(validationErrorKey, true);
});
}
function setInvalid(form, itemIndexes) {
itemIndexes.forEach(function (index) {
form[fieldNamePrefix + index].$setValidity(validationErrorKey, false);
});
}
function getModelControllers(form) {
var ctrls = [];
Object.getOwnPropertyNames(form).forEach(function (key) {
if (key.indexOf(fieldNamePrefix) === 0 && form[key].hasOwnProperty('$setValidity')) {
ctrls.push(form[key]);
}
});
return ctrls;
}
}
})();
label {
display: block;
margin-bottom:6px;
}
input,textarea,select {
display: block;
margin-bottom:6px;
}
ul {
list-style-type: none;
}
ul li {
background-color:#F0F0F0;
padding: 6px;
}
.error {
color: red;
}
.reviewers-validator.ng-invalid {
color:red;
border:solid 2px red;
}
(function () {
'use strict';
angular
.module('app')
.service('uniqueRouteValidator', uniqueRouteValidator);
function uniqueRouteValidator() {
this.validate = validate;
var fieldNamePrefix = 'route_';
var validationErrorKey = 'uniqueRoute';
function validate(form, collection) {
//console.log('collection', collection);
if (!collection) {
return true;
}
var invalidItems = getInvalidItems(collection);
if (!invalidItems || invalidItems.length === 0) {
setValid(form);
return true;
}
setInvalid(form, invalidItems);
return false;
}
function getInvalidItems(reviewers) {
var routes = [];
reviewers.forEach(function (reviewer) {
routes.push(reviewer.route);
});
var counts = {};
routes.forEach(function (route) {
counts[route] = (counts[route] || 0) + 1;
});
var duplicates = [];
reviewers.forEach(function (reviewer, index) {
if (counts[reviewer.route] > 1) {
duplicates.push(index);
}
});
return duplicates;
}
function setValid(form) {
var ctrls = getModelControllers(form);
ctrls.forEach(function (ctrl) {
ctrl.$setValidity(validationErrorKey, true);
});
}
function setInvalid(form, itemIndexes) {
itemIndexes.forEach(function (index) {
form[fieldNamePrefix + index].$setValidity(validationErrorKey, false);
});
}
function getModelControllers(form) {
var ctrls = [];
Object.getOwnPropertyNames(form).forEach(function (key) {
if (key.indexOf(fieldNamePrefix) === 0 && form[key].hasOwnProperty('$setValidity')) {
ctrls.push(form[key]);
}
});
return ctrls;
}
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment