AngularJS trigger field validation after form loads - angularjs

I have set of data fields.
They look like this:
// Read-Only
<div class="field-group" ng-if="feature.edit == false">
<div class="label" ng-class="{'feature-required': field.Validations.Required === true}">{{field.Label}}</div>
<div class="value">{{field.Value}}</div>
</div>
// Editor
<div class="field-group" ng-show="feature.edit == true">
<label for="F{{field.FieldID}}">
<span ng-class="{'feature-required': field.Validations.Required === true, 'feature-notrequired': field.Validations.Required === false}">{{field.Label}}</span>
<input type="text"
id="F{{field.FieldID}}"
name="F{{field.FieldID}}"
ng-change="onFieldUpdate()"
ng-model="field.Value"
jd-field-attributes attr-field="field"
jd-validate on-error="onFieldError"
field="field">
</label>
</div>
feature.edit is controlled by button and you can have data read-olny or editable. Each field has some validation, usually, if required it must be different than null.
I want to trigger that validation after I click edit and input fields show up.
One way to do it is to loop through all input fields and use jQuery trigger("change"). I have to do it with some delay (it takes Angular to populate all fields).
Is there any way to trigger ng-change or run onFieldUpdate(), after that input becomes visible?
I have tried ng-init, but it didn't work.

You could move your validation logic to custom validators in the ngModel $validators pipeline. Functions you add to this pipeline will evaluate against the input model value every time it changes and automatically add the associated valid/invalid classes to the input.
Here's an example of how you can add custom validators:
app.directive('moreValidation', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attributes, ngModel) {
ngModel.$validators.first = function(input) {
return true; // add your custom logic here
}
ngModel.$validators.second = function(input) {
return true;
}
ngModel.$validators.third = function(input) {
return true;
}
}
};
});
// markup
<input type="text" ng-model="modelObject" more-validation />
Here's a small working plnkr example: http://plnkr.co/edit/mKKuhNqcnGXQ4sMePrym?p=preview

Related

compare two input field values in a table using angular

I am a beginner in angularjs and I am trying to make a dynamic table but with custom validations. for instance, I have 2 input fields namely totalSalary and pension paid. The pension paid entered should not be greater than the totalSalary. I am trying to build a custom directive for the pension paid field but how to get the value of the totalSalary field to do the comparison?
Thanks to help.
Ashley
Updated:
This is the table I have made so far as per the next link. Dynamic table made so far
Below are two fields I need to compare.
<td><input type="text" class="form-control" ng-model="employee.details.salary" fcsa-number required/></td>
<td><input type="text" class="form-control" ng-model="employee.details.pension" fcsa-number not-greater-than-yearly-sal required/></td>
The directive so far I have worked on is as per below. I got the value of pension1 but now how to get the value of salary. On leaving the input for pension1, if the amount is greater than the salary, it should prompt the user and clear the value.
angular.module('app').directive("notGreaterThanYearlySal", function () {
return {
restrict: "A",
require: "?ngModel",
link: function (scope, element, attributes, ngModel) {
element.bind('blur', function(event){
alert(element.val());
});
}
};
});
One way you achieve with custom directives
for example here i match email :
<p>Email:<input type="email" name="email1" ng-model="emailReg">
Repeat Email:<input type="email" name="email2" ng-model="emailReg2" ng-match="emailReg"></p>
<span data-ng-show="myForm.emailReg2.$error.match">Emails have to match!</span>
Without using custom Directives :
<button ng-click="add()></button>
<span ng-show="IsMatch">Emails have to match!</span>
$scope.add = function() {
if ($scope.emailReg != $scope.emailReg2) {
$scope.IsMatch=true;
return false;
}
$scope.IsMatch=false;
}
I think this is a really neat way of doing this. You could set something like ng-max to the second input and set it's value as the other inputs ng-model. Then according to this you could show a error message.
<form name="numberForm">
First input:
<input type="number" name="number1" ng-model="number1">
Second input:
<input type="number" name="number2" ng-model="number2" ng-max="number1">
<span ng-show="numberForm.number2.$error.max">This field should not be larger than the other</span>
</form>
Here's a JSFiddle: https://jsfiddle.net/thepio/dpgfuy06/
No need for directive!
Make the following changes in your code:-
Use ng-repeat to iterate over your array of elements in the employee details like this:-
<div ng-repeat ="details in employee.details">
</div>
where all your elements should be put in the employee.details array
Accordingly change your ng-model to "details.pension" and "details.salary"
Make a div below your input fields and specify the condition you want in ng-show
and display an error message for the same.
For instance, in your case it would look like:-
<div ng-show="details.pension > details.salary">Pension cannot be greater than salary!
</div>
This should do the validations there and then itself.
Using custom directive:
.directive("compareTo", function () {
return {
require: "ngModel",
scope: {
otherModelValue: "=compareTo"
},
link: function (scope, element, attributes, ngModel) {
ngModel.$validators.compareTo = function (modelValue) {
return modelValue == scope.otherModelValue;
};
scope.$watch("otherModelValue", function () {
ngModel.$validate();
});
}
};
})

What's the point of $validators and $setValidity?

I think I'm missing something with $validators and $setValidity (which I understand, do the exact same thing so you don't need both - please correct me if I'm wrong). Whether I have the $validators statement in there or not, I get the ng-invalid class added to the input form, which is adding a red border around the input. So why do I need the $validators? I am trying to set the parent form to be invalid if a user does not select a row from the dropdownnin the directive template. I don't want to show any error messages or anything, I just want to add the invalid class and red border based on if a row in the dropdown was selected.
Should I be using $validators or $setValidity? Do I need both $validator and $setValidity like I have below? Also, does $setValidity required ngModelCtrl or not? I get undefined for setValidity if it is not inside the $validators function. Any help is appreciated.
If I want to make the parent form invalid as a whole if no selection is made and I am getting ng-invalid class when I touch and then blur if no selection is made without $validators and $setValidity, then why do I need the $validators and $setValidity??
index.html
<form name="myForm">
<validator
rows="[{name:'tom', city:'san fran', state: 'mn', zip: 34212},
{name: 'joe', city:'san fran', state: 'mn', zip: 45675}]"
ng-required="true"
ng-model="hey">
</validator>
</form>
validate.js - DDO
return {
restrict: 'E',
require: {
ngModelCtrl: 'ngModel',
formCtrl: '?^form'
},
replace: true,
templateUrl: 'view.html',
scope: {},
controllerAs: 'ctrl',
bindToController: {
rows: '=',
onSelected: '&?', //passsed selected row outside component
typedText: '&?', //text typed into input passed outside so
//developer can create a custom filter,
//overriding the auto
textFiltered: '#?', //text return from the custom filter
ngRequired: "=?" //default false, when set to true the component
//needs to validate that something was selected on blur.
//The selection is not put into the input element all the
//time so it can't validate based on whether or not
//something is in the input element itself.
//I need to validate inside the controller where I can see
//if 'this.ngModel' (selectedRow - not passed through scope)
//is undefined or not.
},
controller: 'validatorController'
};
.
function validatorController () {
var ctrl = this;
var rowWasSelected = false;
var input = ctrl.formCtrl.inputField;
//called via ng-click on the dropdown row
//if this is called a row was selected
ctrl.rowSelected = function (row){
rowWasSelected = true;
}
//called via ng-blur of the input element
ctrl.ngModelCtrl.$validators.invalidInput = function (modelValue, viewValue) {
return rowWasSelected;
}
ctrl.$onInit = $onInit; //angular will execute this after
//all conrollers have been initialized, only safe to use
//bound values (through bindToController) in
//the $onInit function.
//i understand this need to be there with Angular 1.5
//using ngModel in the controller
//but I really only need to validate on ng-blur of the input
function $onInit() {
ctrl.validateInput();
}
}
};
}
view.html - template for directive
<div class="dropdown" ng-class="{'open' : ctrl.isOpen}">
<div class="form-group">
<input type="text" class="form-control" name="inputField"
placeholder="select" ng-click="ctrl.openDropdown()"
ng-blur="ctrl.validateInput()"
ng-model="ctrl.currentRow.name"
ng-required="ctrl.ngRequired">
</div>
<ul class="dropdown-menu list-group">
<li class="list-group-item" ng-repeat="row in ctrl.rows"
ng-click="ctrl.onSelectedLocal(row)">
{{row.name}}
</li>
</ul>
</div>
I must be getting the invalid class regardless of the $validators function I have because it is adding the ng-invalid class whether its there or not??
$setValidity(validationErrorKey, isValid);
Change the validity state, and notify the form.
This method can be called within $parsers/$formatters or a custom validation implementation. However, in most cases it should be sufficient to use the ngModel.$validators and ngModel.$asyncValidators collections which will call $setValidity automatically.1

Unable to use ng-minlength for input with custom directive in Angular

I've based a phone number formatting directive on this gist. Everything generally works great. But if I add a ng-minlength or ng-maxlength validation requirement, the input won't accept any input at all.
.directive('phonenumberDirective', ['$filter', function($filter) {
function link(scope, element, attributes) {
scope.inputValue = scope.phonenumberModel;
scope.$watch('inputValue', function(value, oldValue) {
value = String(value);
var number = value.replace(/[^0-9]+/g, '');
scope.phonenumberModel = number;
scope.inputValue = $filter('phonenumber')(number);
});
}
return {
link: link,
restrict: 'E',
scope: {
phonenumberPlaceholder: '=placeholder',
phonenumberModel: '=model'},
template: '<input ng-model="inputValue" type="tel" id="phone" name="phone" ng-minlength="7" class="phonenumber form-control" placeholder="{{phonenumberPlaceholder}}" required /> '
}
}]);
HTML:
<label class="control-label" for="phone">Phone</label>
<phonenumber-directive placeholder="'(000) 000-0000'" model='contactForm.contact.phone'></phonenumber-directive>
<span ng-show="myForm.phone.$error.required && !myForm.phone.$untouched
|| myForm.phone.$error.required && !myForm.phone.$untouched
|| myForm.phone.$error.minlength && !myForm.phone.$untouched" class="help-block">
Enter your phone number</span>
If I understand your question correctly, this behavior is expected. Your ng-model won't be updated if the input does not pass through the validation pipeline.
In this case, you won't see your watcher get fired until ng-minlength is met.
As an aside, you may want to consider using ngModelController on an attribute-level directive (yours is an element directive) as a more "Angular" way to maintain differences between the view value and its underlying model value. You are currently updating inputValue inside a watcher that is looking for updates to inputValue, which may lead to unexpected behavior.

extending angularjs form controller

I have multiple child ng-form which are part of parent ng-form, I want to set the $submitted status of all the child forms, when I set the parent ng-form to $submitted status.
As of now no such method is available on the form-controller, checked here
Lets say, if I want to extend the current form controller to do this, how should I do that? how do I add a new method $setChildFormsToSubmittedState() ? of course I want to do it without disturbing/touching the angular code.
Is it possible? I think it should be given all the child forms hook into parent form using $addControl();.
No idea from where to start.
You can create a directive that appends a functionality to the form controller. Simply create a method that iterates over all the controls by checking if an item has a $$parentForm property that is equal to the form object it belongs to.
DEMO
Javascript
.directive('myForm', function() {
return {
require: 'form',
link: function(scope, elem, attr, form) {
form.__setSubmitted = function() {
setSubmitted(form);
};
function setSubmitted(form) {
form.$submitted = true;
angular.forEach(form, function(item) {
if(item && item.$$parentForm === form) {
setSubmitted(item);
}
});
}
}
};
});
HTML
<form name="myForm" my-form ng-submit="myForm.__setSubmitted()">
<ng-form name="mySubForm1">
<input type="text" ng-model="data.something1" name="something">
{{mySubForm1.$submitted}}
</ng-form>
<br>
<ng-form name="mySubForm2">
<input type="text" ng-model="data.something2" name="something">
{{mySubForm2.$submitted}}
</ng-form>
<br>
<button type="submit">Button</button>
{{myForm.$submitted}}
</form>

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