Angular.js rewrite ngShow function to directive - angularjs

I'm looking to change the functionality defined in the ng-show directive I have on the form fields to display issues with a form field.
For example:
<span class="help-inline" ng-show="showError(schoolSignup.last_name, 'required')">This field is required</span>
Where schoolSignup.last_name is the reference to the model controller for the field, and 'required' is the validation property I'm looking for.
Defined in a controller as
$scope.showError = function(ngModelController, error) {
return ngModelController.$dirty && ngModelController.$error[error];
};
I've been banging my head against a wall trying to work out how to move this to a directive so I don't have to re-define this in every controller. I was thinking of defining it like...
<span class="help-inline" show-error="required" field="schoolSignup.last_name">This field is required</span>
This is as far as I've got
.directive('showError', function () {
return {
restrict: 'A',
scope:{
field: "="
},
link: function(scope, elem, attrs, ctrl)
{
var errorType = attrs.showError;
scope.errors = scope.field.$error[errorType];
// NOT WORKING YET
}
};
});
How can I do this??

You're on the right track; however, the dot in formName.fieldName will give you trouble using the object[field] syntax. Instead, you could do this pretty nicely with the $parse service (docs):
app.directive("showError", function($parse) {
return {
link: function(scope, elem, attrs) {
// Returns a function that, when called with a scope,
// evaluates the expression in `attrs.field` (e.g.
// "schoolSignup.last_name") on the scope.
var getter = $parse(attrs.field);
// Every time a digest cycle fires...
scope.$watch(function() {
// ...get the input field specified in the `field` attribute...
var field = getter(scope);
// ...and check to see if the error specified by the
// `show-error` attribute is set.
if (field.$dirty && field.$error[attrs.showError]) {
elem.show();
} else {
elem.hide();
}
});
}
};
});
Here's a working example: http://jsfiddle.net/BinaryMuse/Lh7YY/

Related

Angular - Cannot get custom directive to cooperate with ng-change

I have a very simple directive (named cooperate-with-ng-change) which requires ng-model and I'd like it to cooperate with ng-change.
Restated, I'd like to be able to do <cooperate-with-ng-change ng-model="model" ng-change="changeHandler()"></cooperate-with-ng-change>
However I'm noticing when changeHandler() is fired, model is the old value and not the new one.
Here's the directive definition object:
return {
restrict: "E",
require: ["ngModel"],
scope: {
"value": "=ngModel"
},
template: "<label>Cooperate with NgChange: <input type='text' ng-model='value' ng-model-options='{debounce: 500}' /></label>",
link: function(scope, element, attrs, ctrls) {
var inputNgModelCtrl = element.find("input").controller("ngModel");
var parentNgModelCtrl = ctrls[0];
Array.prototype.push.apply(inputNgModelCtrl.$viewChangeListeners, parentNgModelCtrl.$viewChangeListeners)
//if I wrap all the parentNgModelCtrl.$viewChangeListeners in a timeout and digest in changeHandler
//then message matches afterDebounce
/*Array.prototype.push.apply(inputNgModelCtrl.$viewChangeListeners,
parentNgModelCtrl.$viewChangeListeners.map(
function(listener) {
return function() {
setTimeout(listener, 1)
}
}))
*/
}
}
Here's a plunker link if you want to play with it
My guess is $viewChangeListeners gets called before the model value is set, however I can't find what might correspond for after the model value is set.

angularjs directive - get element bound text content

How do you get the value of the binding based on an angular js directive restrict: 'A'?
<span directiverestrict> {{binding}} </span>
I tried using elem[0].innerText but it returns the exact binding '{{binding}}' not the value of the binding
.directive('directiverestrict',function() {
return {
restrict:'A',
link: function(scope, elem, attr) {
// I want to get the value of the binding enclosed in the elements directive without ngModels
console.log(elem[0].textContent) //----> returns '{{binding}}'
}
};
});
You can use the $interpolate service, eg
.directive('logContent', function($log, $interpolate) {
return {
restrict: 'A',
link: function postLink(scope, element) {
$log.debug($interpolate(element.text())(scope));
}
};
});
Plunker
<span directiverestrict bind-value="binding"> {{binding}} </span>
SCRIPT
directive("directiverestrict", function () {
return {
restrict : "A",
scope : {
value : '=bindValue'
},
link : function (scope,ele,attr) {
alert(scope.value);
}
}
});
During the link phase the inner bindings are not evaluated, the easiest hack here would be to use $timeout service to delay evaluation of inner content to next digest cycle, such as
$timeout(function() {
console.log(elem[0].textContent);
},0);
Try ng-transclude. Be sure to set transclude: true on the directive as well. I was under the impression this was only needed to render the text on the page. I was wrong. This was needed for me to be able to get the value into my link function as well.

Angular validation of dependent text field in an ngRepeat tag

I have the following ngRepeat code:
<div ng-repeat="drug in drugs | orderBy:'drugName'">
<input id="{{drug.drugName}}"
class="drugCheckbox"
name="{{drug.drugName}}"
type="checkbox" value="{{drug.drugName}}"
ng-model="foobar"
validate-foo
/>
{{drug.drugName}}
<!-- this is the error message, one per each repeat element -->
<span style="color:red" ng-show="myForm.{{drug.drugName}}.$error.summary">
Fill in the summary
</span>
<input type="text"
name="summary-{{drug.drugName}}"
id="summary-{{drug.drugName}}"
placeholder="Summary"/>
</div>
My validateFoo directive:
app.directive('validateFoo', function(){
return{
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attr, ctrl) {
ctrl.$parsers.unshift(function (viewValue) {
var id= attr.id;
//if the checkbox is checked, see if the text field has been filled in
if(viewValue) {
var val= document.getElementById('summary-' + id).value;
if(val.length == 0) {
ctrl.$setValidity("summary", false);
return undefined;
}
else {
ctrl.$setValidity(id, true);
return viewValue;
}
}
});
}
}
});
I cannot get the validation to work for any of the checkboxes, I am not clear on what to use for "ng-show" in the error message span element.
First of all that's because all fields will be registered in your formController under name {{drug.drugName}}.
Second thing is that there is no myForm.{{drug.drugName}}.$invalid in scope. You probably tried to do myForm[drug.drugName].$invalid. But it will not work anyway - look at 1.
I think that there still is no proper way to set name field dynamically in ng-repeat. Instead you need to create tour own directive, that will build repeated list elements as String, then append it to your HTML and $compile it place.
EDIT
I have developed one solution:
http://jsfiddle.net/ulfryk/gejfeLp7/
EDIT 2:
I've developed even simpler solution ( http://jsfiddle.net/ulfryk/5wq7539r/ ):
app.directive('fixRepeatedModelName', function () {
return {
link: function (scope, element, attrs, ngModelCtrl) {
if (!attrs.name) return;
ngModelCtrl.$name = attrs.name;
},
priority: '-100',
require: 'ngModel'
};
});
And add fix-repeated-model-name attribute to any ng-model with dynamic ({{ }} dependent) name placed inside ng-repeat. And it now works :)

How to disable angulars type=email validation?

How would you go about disabling, or at the very least changing, how Angular validates type=email inputs?
Currently, if you use type=email, Angular essentially double validates.. as the Browser (Chrome in this case) validates the email, and then angular does too. Not only that, but what is valid in Chrome foo#bar is not valid in Angularjs.
The best i could find, is ng-pattern, but ng-pattern simply adds a 3rd pattern validation for the input type.. instead of replacing Angular's email validation. heh
Any ideas?
Note: This is example is for angular 1.2.0-rc.3. Things might behave differently on other versions
Like others have stated it is a bit complex to turn off angulars default input validation. You need to add your own directive to the input element and handle things in there. Sergey's answer is correct, however it presents some problems if you need several validators on the element and don't want the built in validator to fire.
Here is an example validating an email field with a required validator added. I have added comments to the code to explain what is going on.
Input element
<input type="email" required>
Directive
angular.module('myValidations', [])
.directive('input', function () {
var self = {
// we use ?ngModel since not all input elements
// specify a model, e.g. type="submit"
require: '?ngModel'
// we need to set the priority higher than the base 0, otherwise the
// built in directive will still be applied
, priority: 1
// restrict this directive to elements
, restrict: 'E'
, link: function (scope, element, attrs, controller) {
// as stated above, a controller may not be present
if (controller) {
// in this case we only want to override the email validation
if (attrs.type === 'email') {
// clear this elements $parsers and $formatters
// NOTE: this will disable *ALL* previously set parsers
// and validators for this element. Beware!
controller.$parsers = [];
controller.$formatters = [];
// this function handles the actual validation
// see angular docs on how to write custom validators
// http://docs.angularjs.org/guide/forms
//
// in this example we are not going to actually validate an email
// properly since the regex can be damn long, so apply your own rules
var validateEmail = function (value) {
console.log("Validating as email", value);
if (controller.$isEmpty(value) || /#/.test(value)) {
controller.$setValidity('email', true);
return value;
} else {
controller.$setValidity('email', false);
return undefined;
}
};
// add the validator to the $parsers and $formatters
controller.$parsers.push(validateEmail);
controller.$formatters.push(validateEmail);
}
}
}
};
return self;
})
// define our required directive. It is a pretty standard
// validation directive with the exception of it's priority.
// a similar approach must be take with all validation directives
// you would want to use alongside our `input` directive
.directive('required', function () {
var self = {
// required should always be applied to a model element
require: 'ngModel'
, restrict: 'A'
// The priority needs to be higher than the `input` directive
// above, or it will be removed when that directive is run
, priority: 2
, link: function (scope, element, attrs, controller) {
var validateRequired = function (value) {
if (value) {
// it is valid
controller.$setValidity('required', true);
return value;
} else {
// it is invalid, return undefined (no model update)
controller.$setValidity('required', false);
return undefined;
}
};
controller.$parsers.push(validateRequired);
}
};
return self;
})
;
There you have it. You now have control over type="email" input validations. Please use a proper regex to test the email though.
One thing to note is that in this example validateEmail is run before validateRequired. If you need validateRequired to run before any other validations, then just prepend it to the $parsers array (using unshift instead of push).
Very simple. I had to alter the email regex to match a business requirement, so I made this directive that makes the email regex customizable. It essentially overwrites the original validator with my custom one. You don't have to mess with all the $parsers and $formatters (unless I'm missing something). So my directive was this...
module.directive('emailPattern', function(){
return {
require : 'ngModel',
link : function(scope, element, attrs, ngModel) {
var EMAIL_REGEX = new RegExp(attrs.emailPattern, "i");
ngModel.$validators["email"] = function (modelValue, viewValue) {
var value = modelValue || viewValue;
return ngModel.$isEmpty(value) || EMAIL_REGEX.test(value);
};
}
}
});
Then use it like this, supplying whatever email pattern you personally want:
<input type="email" email-pattern=".+#.+\..+"/>
But if you just want to permanently disable it then you could do this.
module.directive('removeNgEmailValidation', function(){
return {
require : 'ngModel',
link : function(scope, element, attrs, ngModel) {
ngModel.$validators["email"] = function () {
return true;
};
}
}
});
Then use it like this...
<input type="email" remove-ng-email-validation>
On HTML5 you can use the form's attribute novalidate to disable browser's validation:
<form novalidate>
<input type="email"/>
</form>
If you want to create a custom validator in angularjs, you have a good tutorial and example here: http://www.benlesh.com/2012/12/angular-js-custom-validation-via.html
Echoing nfiniteloop, you don't need to mess with the $parsers or $formatters to override the default validators. As referenced in the Angular 1.3 docs, the $validators object is accessible on the ngModelController. With custom directives you can write as many different email validation functions as you need and call them wherever you want.
Here's one with a very nice standard email format regex from tuts: 8 Regular Expressions You Should Now (probably identical to Angular's default, idk).
var app = angular.module('myApp', []);
app.directive('customEmailValidate', function() {
return {
require: 'ngModel',
link: function(scope, elm, attrs, ctrl) {
var EMAIL_REGEXP = /^([a-z0-9_\.-]+)#([\da-z\.-]+)\.([a-z\.]{2,6})$/;
ctrl.$validators.email = function(modelValue, viewValue) {
if (ctrl.$isEmpty(modelValue)) {
// consider empty models to be valid
return true;
}
if (EMAIL_REGEXP.test(viewValue)) {
// it is valid
return true;
}
// it is invalid
return false;
};
}
};
});
Here's one that removes validation entirely:
var app = angular.module('myApp', []);
app.directive('noValidation', function() {
return {
require: 'ngModel',
link: function(scope, elm, attrs, ctrl) {
ctrl.$validators.email = function(modelValue, viewValue) {
// everything is valid
return true;
};
}
};
});
To use in your markup:
<!-- 'test#example.com' is valid, '#efe#eh.c' is invalid -->
<input type="email" custom-email-validate>
<!-- both 'test#example.com' and '#efe#eh.c' are valid -->
<input type="email" no-validation>
In my project I do something like this (custom directive the erases all other validations including ones installed by angularjs):
angular.module('my-project').directive('validEmail', function() {
return {
require: 'ngModel',
link: function(scope, elm, attrs, ctrl){
var validator = function(value){
if (value == '' || typeof value == 'undefined') {
ctrl.$setValidity('validEmail', true);
} else {
ctrl.$setValidity('validEmail', /your-regexp-here/.test(value));
}
return value;
};
// replace all other validators!
ctrl.$parsers = [validator];
ctrl.$formatters = [validator];
}
}
});
How to use it (note novalidate, it's required to turn off browser validation):
<form novalidate>
<input type="email" model="email" class="form-control" valid-email>

How to add custom validation to an AngularJS form?

I have a form with input fields and validation setup by adding the required attributes and such. But for some fields I need to do some extra validation. How would I "tap in" to the validation that FormController controls?
Custom validation could be something like "if these 3 fields are filled in, then this field is required and needs to be formatted in a particular way".
There's a method in FormController.$setValidity but that doesn't look like a public API so I rather not use it. Creating a custom directive and using NgModelController looks like another option, but would basically require me to create a directive for each custom validation rule, which I do not want.
Actually, marking a field from the controller as invalid (while also keeping FormController in sync) might be the thing that I need in the simplest scenario to get the job done, but I don't know how to do that.
Edit: added information about ngMessages (>= 1.3.X) below.
Standard form validation messages (1.0.X and above)
Since this is one of the top results if you Google "Angular Form Validation", currently, I want to add another answer to this for anyone coming in from there.
There's a method in FormController.$setValidity but that doesn't look like a public API so I rather not use it.
It's "public", no worries. Use it. That's what it's for. If it weren't meant to be used, the Angular devs would have privatized it in a closure.
To do custom validation, if you don't want to use Angular-UI as the other answer suggested, you can simply roll your own validation directive.
app.directive('blacklist', function (){
return {
require: 'ngModel',
link: function(scope, elem, attr, ngModel) {
var blacklist = attr.blacklist.split(',');
//For DOM -> model validation
ngModel.$parsers.unshift(function(value) {
var valid = blacklist.indexOf(value) === -1;
ngModel.$setValidity('blacklist', valid);
return valid ? value : undefined;
});
//For model -> DOM validation
ngModel.$formatters.unshift(function(value) {
ngModel.$setValidity('blacklist', blacklist.indexOf(value) === -1);
return value;
});
}
};
});
And here's some example usage:
<form name="myForm" ng-submit="doSomething()">
<input type="text" name="fruitName" ng-model="data.fruitName" blacklist="coconuts,bananas,pears" required/>
<span ng-show="myForm.fruitName.$error.blacklist">
The phrase "{{data.fruitName}}" is blacklisted</span>
<span ng-show="myForm.fruitName.$error.required">required</span>
<button type="submit" ng-disabled="myForm.$invalid">Submit</button>
</form>
Note: in 1.2.X it's probably preferrable to substitute ng-if for ng-show above
Here is an obligatory plunker link
Also, I've written a few blog entries about just this subject that goes into a little more detail:
Angular Form Validation
Custom Validation Directives
Edit: using ngMessages in 1.3.X
You can now use the ngMessages module instead of ngShow to show your error messages. It will actually work with anything, it doesn't have to be an error message, but here's the basics:
Include <script src="angular-messages.js"></script>
Reference ngMessages in your module declaration:
var app = angular.module('myApp', ['ngMessages']);
Add the appropriate markup:
<form name="personForm">
<input type="email" name="email" ng-model="person.email" required/>
<div ng-messages="personForm.email.$error">
<div ng-message="required">required</div>
<div ng-message="email">invalid email</div>
</div>
</form>
In the above markup, ng-message="personForm.email.$error" basically specifies a context for the ng-message child directives. Then ng-message="required" and ng-message="email" specify properties on that context to watch. Most importantly, they also specify an order to check them in. The first one it finds in the list that is "truthy" wins, and it will show that message and none of the others.
And a plunker for the ngMessages example
Angular-UI's project includes a ui-validate directive, which will probably help you with this. It let's you specify a function to call to do the validation.
Have a look at the demo page: http://angular-ui.github.com/, search down to the Validate heading.
From the demo page:
<input ng-model="email" ui-validate='{blacklist : notBlackListed}'>
<span ng-show='form.email.$error.blacklist'>This e-mail is black-listed!</span>
then in your controller:
function ValidateCtrl($scope) {
$scope.blackList = ['bad#domain.example','verybad#domain.example'];
$scope.notBlackListed = function(value) {
return $scope.blackList.indexOf(value) === -1;
};
}
You can use ng-required for your validation scenario ("if these 3 fields are filled in, then this field is required":
<div ng-app>
<input type="text" ng-model="field1" placeholder="Field1">
<input type="text" ng-model="field2" placeholder="Field2">
<input type="text" ng-model="field3" placeholder="Field3">
<input type="text" ng-model="dependentField" placeholder="Custom validation"
ng-required="field1 && field2 && field3">
</div>
You can use Angular-Validator.
Example: using a function to validate a field
<input type = "text"
name = "firstName"
ng-model = "person.firstName"
validator = "myCustomValidationFunction(form.firstName)">
Then in your controller you would have something like
$scope.myCustomValidationFunction = function(firstName){
if ( firstName === "John") {
return true;
}
You can also do something like this:
<input type = "text"
name = "firstName"
ng-model = "person.firstName"
validator = "'!(field1 && field2 && field3)'"
invalid-message = "'This field is required'">
(where field1 field2, and field3 are scope variables. You might also want to check if the fields do not equal the empty string)
If the field does not pass the validator then the field will be marked as invalid and the user will not be able to submit the form.
For more use cases and examples see: https://github.com/turinggroup/angular-validator
Disclaimer: I am the author of Angular-Validator
I recently created a directive to allow for expression-based invalidation of angular form inputs. Any valid angular expression can be used, and it supports custom validation keys using object notation. Tested with angular v1.3.8
.directive('invalidIf', [function () {
return {
require: 'ngModel',
link: function (scope, elm, attrs, ctrl) {
var argsObject = scope.$eval(attrs.invalidIf);
if (!angular.isObject(argsObject)) {
argsObject = { invalidIf: attrs.invalidIf };
}
for (var validationKey in argsObject) {
scope.$watch(argsObject[validationKey], function (newVal) {
ctrl.$setValidity(validationKey, !newVal);
});
}
}
};
}]);
You can use it like this:
<input ng-model="foo" invalid-if="{fooIsGreaterThanBar: 'foo > bar',
fooEqualsSomeFuncResult: 'foo == someFuncResult()'}/>
Or by just passing in an expression (it will be given the default validationKey of "invalidIf")
<input ng-model="foo" invalid-if="foo > bar"/>
Here's a cool way to do custom wildcard expression validations in a form (from: Advanced form validation with AngularJS and filters):
<form novalidate="">
<input type="text" id="name" name="name" ng-model="newPerson.name"
ensure-expression="(persons | filter:{name: newPerson.name}:true).length !== 1">
<!-- or in your case:-->
<input type="text" id="fruitName" name="fruitName" ng-model="data.fruitName"
ensure-expression="(blacklist | filter:{fruitName: data.fruitName}:true).length !== 1">
</form>
app.directive('ensureExpression', ['$http', '$parse', function($http, $parse) {
return {
require: 'ngModel',
link: function(scope, ele, attrs, ngModelController) {
scope.$watch(attrs.ngModel, function(value) {
var booleanResult = $parse(attrs.ensureExpression)(scope);
ngModelController.$setValidity('expression', booleanResult);
});
}
};
}]);
jsFiddle demo (supports expression naming and multiple expressions)
It's similar to ui-validate, but you don't need a scope specific validation function (this works generically) and ofcourse you don't need ui.utils this way.
#synergetic I think #blesh suppose to put function validate as below
function validate(value) {
var valid = blacklist.indexOf(value) === -1;
ngModel.$setValidity('blacklist', valid);
return valid ? value : undefined;
}
ngModel.$formatters.unshift(validate);
ngModel.$parsers.unshift(validate);
Update:
Improved and simplified version of previous directive (one instead of two) with same functionality:
.directive('myTestExpression', ['$parse', function ($parse) {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ctrl) {
var expr = attrs.myTestExpression;
var watches = attrs.myTestExpressionWatch;
ctrl.$validators.mytestexpression = function (modelValue, viewValue) {
return expr == undefined || (angular.isString(expr) && expr.length < 1) || $parse(expr)(scope, { $model: modelValue, $view: viewValue }) === true;
};
if (angular.isString(watches)) {
angular.forEach(watches.split(",").filter(function (n) { return !!n; }), function (n) {
scope.$watch(n, function () {
ctrl.$validate();
});
});
}
}
};
}])
Example usage:
<input ng-model="price1"
my-test-expression="$model > 0"
my-test-expression-watch="price2,someOtherWatchedPrice" />
<input ng-model="price2"
my-test-expression="$model > 10"
my-test-expression-watch="price1"
required />
Result: Mutually dependent test expressions where validators are executed on change of other's directive model and current model.
Test expression has local $model variable which you should use to compare it to other variables.
Previously:
I've made an attempt to improve #Plantface code by adding extra directive. This extra directive very useful if our expression needs to be executed when changes are made in more than one ngModel variables.
.directive('ensureExpression', ['$parse', function($parse) {
return {
restrict: 'A',
require: 'ngModel',
controller: function () { },
scope: true,
link: function (scope, element, attrs, ngModelCtrl) {
scope.validate = function () {
var booleanResult = $parse(attrs.ensureExpression)(scope);
ngModelCtrl.$setValidity('expression', booleanResult);
};
scope.$watch(attrs.ngModel, function(value) {
scope.validate();
});
}
};
}])
.directive('ensureWatch', ['$parse', function ($parse) {
return {
restrict: 'A',
require: 'ensureExpression',
link: function (scope, element, attrs, ctrl) {
angular.forEach(attrs.ensureWatch.split(",").filter(function (n) { return !!n; }), function (n) {
scope.$watch(n, function () {
scope.validate();
});
});
}
};
}])
Example how to use it to make cross validated fields:
<input name="price1"
ng-model="price1"
ensure-expression="price1 > price2"
ensure-watch="price2" />
<input name="price2"
ng-model="price2"
ensure-expression="price2 > price3"
ensure-watch="price3" />
<input name="price3"
ng-model="price3"
ensure-expression="price3 > price1 && price3 > price2"
ensure-watch="price1,price2" />
ensure-expression is executed to validate model when ng-model or any of ensure-watch variables is changed.
Custom Validations that call a Server
Use the ngModelController $asyncValidators API which handles asynchronous validation, such as making an $http request to the backend. Functions added to the object must return a promise that must be resolved when valid or rejected when invalid. In-progress async validations are stored by key in ngModelController.$pending. For more information, see AngularJS Developer Guide - Forms (Custom Validation).
ngModel.$asyncValidators.uniqueUsername = function(modelValue, viewValue) {
var value = modelValue || viewValue;
// Lookup user by username
return $http.get('/api/users/' + value).
then(function resolved() {
//username exists, this means validation fails
return $q.reject('exists');
}, function rejected() {
//username does not exist, therefore this validation passes
return true;
});
};
For more information, see
ngModelController $asyncValidators API
AngularJS Developer Guide - Forms (Custom Validation).
Using the $validators API
The accepted answer uses the $parsers and $formatters pipelines to add a custom synchronous validator. AngularJS 1.3+ added a $validators API so there is no need to put validators in the $parsers and $formatters pipelines:
app.directive('blacklist', function (){
return {
require: 'ngModel',
link: function(scope, elem, attr, ngModel) {
ngModel.$validators.blacklist = function(modelValue, viewValue) {
var blacklist = attr.blacklist.split(',');
var value = modelValue || viewValue;
var valid = blacklist.indexOf(value) === -1;
return valid;
});
}
};
});
For more information, see AngularJS ngModelController API Reference - $validators.
In AngularJS the best place to define Custom Validation is Cutsom directive.
AngularJS provide a ngMessages module.
ngMessages is a directive that is designed to show and hide messages
based on the state of a key/value object that it listens on. The
directive itself complements error message reporting with the ngModel
$error object (which stores a key/value state of validation errors).
For custom form validation One should use ngMessages Modules with custom directive.Here i have a simple validation which will check if number length is less then 6 display an error on screen
<form name="myform" novalidate>
<table>
<tr>
<td><input name='test' type='text' required ng-model='test' custom-validation></td>
<td ng-messages="myform.test.$error"><span ng-message="invalidshrt">Too Short</span></td>
</tr>
</table>
</form>
Here is how to create custom validation directive
angular.module('myApp',['ngMessages']);
angular.module('myApp',['ngMessages']).directive('customValidation',function(){
return{
restrict:'A',
require: 'ngModel',
link:function (scope, element, attr, ctrl) {// 4th argument contain model information
function validationError(value) // you can use any function and parameter name
{
if (value.length > 6) // if model length is greater then 6 it is valide state
{
ctrl.$setValidity('invalidshrt',true);
}
else
{
ctrl.$setValidity('invalidshrt',false) //if less then 6 is invalide
}
return value; //return to display error
}
ctrl.$parsers.push(validationError); //parsers change how view values will be saved in the model
}
};
});
$setValidity is inbuilt function to set model state to valid/invalid
I extended #Ben Lesh's answer with an ability to specify whether the validation is case sensitive or not (default)
use:
<input type="text" name="fruitName" ng-model="data.fruitName" blacklist="Coconuts,Bananas,Pears" caseSensitive="true" required/>
code:
angular.module('crm.directives', []).
directive('blacklist', [
function () {
return {
restrict: 'A',
require: 'ngModel',
scope: {
'blacklist': '=',
},
link: function ($scope, $elem, $attrs, modelCtrl) {
var check = function (value) {
if (!$attrs.casesensitive) {
value = (value && value.toUpperCase) ? value.toUpperCase() : value;
$scope.blacklist = _.map($scope.blacklist, function (item) {
return (item.toUpperCase) ? item.toUpperCase() : item
})
}
return !_.isArray($scope.blacklist) || $scope.blacklist.indexOf(value) === -1;
}
//For DOM -> model validation
modelCtrl.$parsers.unshift(function (value) {
var valid = check(value);
modelCtrl.$setValidity('blacklist', valid);
return value;
});
//For model -> DOM validation
modelCtrl.$formatters.unshift(function (value) {
modelCtrl.$setValidity('blacklist', check(value));
return value;
});
}
};
}
]);
Some great examples and libs presented in this thread, but they didn't quite have what I was looking for. My approach: angular-validity -- a promise based validation lib for asynchronous validation, with optional Bootstrap styling baked-in.
An angular-validity solution for the OP's use case might look something like this:
<input type="text" name="field4" ng-model="field4"
validity="eval"
validity-eval="!(field1 && field2 && field3 && !field4)"
validity-message-eval="This field is required">
Here's a Fiddle, if you want to take it for a spin. The lib is available on GitHub, has detailed documentation, and plenty of live demos.

Resources