Check duplicate values for a text filed in Angular form - angularjs

Folks,
I have a text-field in a form. When a user enters a value in this text field,
I would like to check it against a array of values to ensure this value doesn't already exists.
I started off writing a directive and since I am new to this I am quiet lost
<input type="text" ng-model="user.name" placeholder="Enter Name" required checkDuplicateName existing-names="allUsers" tooltip/>
I the above code allUsers looks like this
var allUsers = ['john','james','Tim'];
My directive is as follows:
angular.module('formInputs').directive('checkDuplicateName', function() {
return {
restrict : 'A',
require : 'ngModel',
link: function(scope, element, attrs, ctrl) {
scope.$watch(attrs.ngModel,function(){
for (var i =0; i<attrs.existing-names.length;i++) {
if (attrs.existing-names[i] === attrs.ngModel) {
attrs.$set('tooltip','Name already exsists');
return;
}
}
});
}
}
});
The problem I am having is that the directive is not getting fired. What am i missing here ?

I've created a fiddle here that has a working version of your idea. It was just a couple small things, it doesn't look like you are passing your data correctly. When you access attrs it just gives you the string that is in the markup so attrs.ngModel will just give you "users.name" as a string.
I created an isolate scope so that you can see the scoping.
scope: {
model: '=ngModel',
users: '=existingNames'
},
Also, angular turns this-case-in-html into camel case in the js like: thisCaseInHtml
Edit: if you don't want to isolate scope, that's fine too. You just need to be evaluating the strings on the scope instead.
Here's an updated fiddle http://jsfiddle.net/ddJ4Z/10/ with the changes. Notice how the values are evaluated on the scope.
scope.$watch(attrs.ngModel,function(newVal){
var users = scope.$eval(attrs.existingNames) || [];
for (var i =0; i<users.length;i++) {
if (users[i] === newVal) {
window.alert(newVal);
return;
}
}
});
Hope this helps!

Related

How to use the last valid modelValue if a model becomes invalid?

I'm working on an application that saves changes automatically when the user changes something, for example the value of an input field. I have written a autosave directive that is added to all form fields that should trigger save events automatically.
template:
<input ng-model="fooCtrl.name" autosave>
<input ng-model="fooCtrl.email" autosave>
directive:
.directive('autosave', ['$parse', function ($parse) {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ngModel) {
function saveIfModelChanged () {
// save object containing name and email to server ...
}
ngModel.$viewChangeListeners.push(function () {
saveIfModelChanged();
});
}
};
}]);
So far, this all works fine for me. However, when I add validation into the mix, for example validating the input field to be a valid email address, the modelValue is set to undefined as soon as the viewValue is changed to an invalid email address.
What I would like to do is this: Remember the last valid modelValue and use this when autosaving. If the user changes the email address to be invalid, the object containing name and email should still be saved to the server. Using the current valid name and the last valid email.
I started out by saving the last valid modelValue like this:
template with validation added:
<input type="email" ng-model="fooCtrl.name" autosave required>
<input ng-model="fooCtrl.email" autosave required>
directive with saving lastModelValue:
.directive('autosave', ['$parse', function ($parse) {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ngModel) {
var lastModelValue;
function saveIfModelChanged () {
// remeber last valid modelValue
if (ngModel.$valid) {
lastModelValue = ngModel.$modelValue;
}
// save object containing current or last valid
// name and email to server ...
}
ngModel.$viewChangeListeners.push(function () {
saveIfModelChanged();
});
}
};
}]);
My question is, how to use lastModelValue while saving, but preserving the invalid value in the view?
EDIT:
Another possibility, as suggested by Jugnu below, would be wrapping and manipulating the build in validators.
I tried to following: wrap all existing validators and remember the last valid value, to restore it if validations fails:
Object.keys(ngModel.$validators).forEach(function(validatorName, index) {
var validator = ngModel.$validators[validatorName];
ngModel.$validators[validatorName] = createWrapper(validatorName, validator, ngModel);
});
function createWrapper(validatorName, validator, ngModel){
var lastValid;
return function (modelValue){
var result = validator(modelValue);
if(result) {
lastValid = modelValue;
}else{
// what to do here? maybe asign the value like this:
// $parse(attrs.ngModel).assign(scope, lastValid);
}
return result;
};
}
But I'm not sure how to continue with this approach either. Can I set the model value without AngularJS kicking in and try to validate that newly set value?
I have created a simple directive that serves as a wrapper on the ng-model directive and will keep always the latest valid model value. It's called valid-ng-model and should replace the usage of ng-model on places where you want to have the latest valid value.
I've created an example use case here, I hope you will like it. Any ideas for improvements are welcomed.
This is the implementation code for valid-ng-model directive.
app.directive('validNgModel', function ($compile) {
return {
terminal: true,
priority: 1000,
scope: {
validNgModel: '=validNgModel'
},
link: function link(scope, element, attrs) {
// NOTE: add ngModel directive with custom model defined on the isolate scope
scope.customNgModel = angular.copy(scope.validNgModel);
element.attr('ng-model', 'customNgModel');
element.removeAttr('valid-ng-model');
// NOTE: recompile the element without this directive
var compiledElement = $compile(element)(scope);
var ngModelCtrl = compiledElement.controller('ngModel');
// NOTE: Synchronizing (inner ngModel -> outside valid model)
scope.$watch('customNgModel', function (newModelValue) {
if (ngModelCtrl.$valid) {
scope.validNgModel = newModelValue;
}
});
// NOTE: Synchronizing (outside model -> inner ngModel)
scope.$watch('validNgModel', function (newOutsideModelValue) {
scope.customNgModel = newOutsideModelValue;
});
}
};
});
Edit: directive implementation without isolate scope: Plunker.
Since you are sending the entire object for each field modification, you have to keep the last valid state of that entire object somewhere. Use case I have in mind:
You have a valid object { name: 'Valid', email: 'Valid' }.
You change the name to invalid; the autosave directive placed at the name input knows its own last valid value, so the correct object gets sent.
You change the email to invalid too. The autosave directive placed at the email input knows its own last valid value but NOT that of name. If the last known good values are not centralized, an object like { name: 'inalid', email: 'Valid' } will be sent.
So the suggestion:
Keep a sanitized copy of the object you are editing. By sanitized I mean that any invalid initial values should be replaced by valid pristine ones (e.g. zeros, nulls etc). Expose that copy as a controller member, e.g. fooCtrl.lastKnowngood.
Let autosave know the last known good state, e.g. as:
<input ng-model="fooCtrl.email" autosave="fooCtrl.lastKnowngood" required />
Keep the last known good local value in that object; utilize the ng-model expression, e.g. as:
var lastKnownGoodExpr = $parse(attrs.autosave);
var modelExpr = $parse(attrs.ngModel);
function saveIfModelChanged () {
var lastKnownGood = lastKnownGoodExpr(scope);
if (ngModel.$valid) {
// trick here; explanation later
modelExpr.assign({fooCtrl: lastKnownGood}, ngModel.$modelValue);
}
// send the lastKnownGood object to the server!!!
}
Send the lastKnownGood object.
The trick, its shortcomings and how can it be improved: When setting the local model value to the lastKnownGood object you use a context object different than the current scope; this object assumes that the controller is called fooCtrl (see the line modelExpr.assign({fooCtrl: lastKnownGood}, ...)). If you want a more general directive, you may want to pass the root as a different attribute, e.g.:
<input ng-model="fooCtrl.email" autosave="fooCtrl.lastKnowngood" required
autosave-fake-root="fooCtrl" />
You may also do some parsing of the ng-model expression yourself to determine the first component, e.g. substring 0 → 1st occurence of the dot (again simplistic).
Another shortcoming is how you handle more complex paths (in the general case), e.g. fooCtrl.persons[13].address['home'].street - but that seems not to be your use case.
By the way, this:
ngModel.$viewChangeListeners.push(function () {
saveIfModelChanged();
});
can be simplified as:
ngModel.$viewChangeListeners.push(saveIfModelChanged);
Angular default validators will only assign value to model if its valid email address.To overcome that you will need to override default validators.
For more reference see : https://docs.angularjs.org/guide/forms#modifying-built-in-validators
You can create a directive that will assign invalide model value to some scope variable and then you can use it.
I have created a small demo for email validation but you can extend it to cover all other validator.
Here is fiddle : http://plnkr.co/edit/EwuyRI5uGlrGfyGxOibl?p=preview

In AngularJS, how to force the re-validation of a field in a form when another value in the same form is changed?

I have a form with few fields, however a select and an input field are coupled: the validation on the input depends on which value the user chooses in the select field.
I'll try to clarify with an example. Let's say that the select contains names of planets:
<select id="planet" class="form-control" name="planet" ng-model="planet" ng-options="c.val as c.label for c in planets"></select>
in the input I apply custom validation via a custom directive named "input-validation":
<input id="city" input-validation iv-allow-if="planet==='earth'" class="form-control" name="city" ng-model="city" required>
where this is the directive:
.directive('inputValidation', [function() {
return {
require: 'ngModel',
restrict: 'A',
scope: {
ivAllowIf: '='
},
link: function(scope, elm, attrs, ctrl) {
ctrl.$parsers.unshift(function(viewValue) {
//input is allowed if the attribute is not present or the expression evaluates to true
var inputAllowed = attrs.ivAllowIf === undefined || scope.$parent.$eval(attrs.ivAllowIf);
if (inputAllowed) {
ctrl.$setValidity('iv', true);
return viewValue;
} else {
ctrl.$setValidity('iv', false);
return undefined;
}
});
}
};
}])
The full example can be examined in Plnkr: http://plnkr.co/edit/t2xMPy1ehVFA5KNEDfrf?p=preview
Whenever the select is modified, I need the input to be verified again. This is not happening in my code. What am I doing wrong?
I have done the same thing for validation of start-date on change of end-date. In the directive of start-date add watch for change of end-date and then call ngModel.$validate() in case end-date new value is defined.
scope.$watch(function () {
return $parse(attrs.endDate)(scope);
}, function () {
ngModel.$validate();
});
The important part to take is call to ngModel.$validate() inside the directive.
Note
you should use $validators for custom validations above to work. read here, $parsers is the old way - from angularjs 1.3 use $validators
FIXED PLUNKER LINK

Keeping Angular form validation DRY

I have a form that has maybe 15-20 fields. Each one of them contains an attribute that looks something like this:
ng-class='addressForm.city.$invalid && addressForm.city.$touched ? "error" : ""'
So I have that long string of code repeated 15-20 times. This sets my DRY alarm bells off bigtime.
I could devise my own way to make this more DRY but I don't want to re-invent the wheel. Is there a gererally-accepted way of keeping Angular form validation DRY?
If it's just for styling use CSS.
Angular will add .ng-invalid and .ng-touched to input elements that are invalid and touched.
Or you could wrap the whole thing in a directive something like
angular.module('module').directive('errorClass', function(){
return{
require: 'ngModel',
link: function(scope, el, attr, model) {
function setClass() {
if(model.$touched && model.$invalid) {
if(!el.hasClass('error')) {
el.addClass('error');
}
} else {
el.removeClass('error');
}
}
scope.$watch(function(){ return model.$touched; }, setClass);
scope.$watch(function(){ return model.$invalid; }, setClass);
}
}
});
Also i havn't actually used this directive, so it may need some tweaking.
As #lain said, you don't have to add another class (like error) if the field is invalid, Angular adds that for you by default, it's just the name that differs (ng-invalid).
You can see how is that used here (the official form example from Angular).
If you still want to do this in your way, this is the implementation of my latest comment, using ngChange directive.
The html:
<input type="text" ng-model="addressForm.city" required ng-change="fieldChanged(this, 'city')">
The change event:
$scope.fieldChanged = function(el, fieldName){
if($scope.addressForm[fieldName].$invalid && $scope.addressForm[fieldName].$touched) angular.element(el).addClass('error');
else angular.element(el).removeClass('error');
}
This is not good practice (to manipulate the DOM in the controller), you should implement that in a directive, but binding a directive to each field would add watchers and I, personally, try to avoid as much as possible using too many watchers.
A more elegant option would be to combine this ngChange with ngClass or simply go with that simple DOM manipulation within controller. It's your choise :)
I ended up creating my own directive for this. I believe the following directive, when applied, will behave equivalently to this:
form(name='addressForm')
input(
type='text'
name='city'
ng-class='addressForm.city.$invalid && (addressForm.city.$touched || addressForm.$submitted) ? "error" : ""'
)
Instead of all that, I can do:
form(name='addressForm')
input(
type='text'
name='city'
validate-for='addressForm'
)
The directive will check validity on:
Blur
Form submission
Value change
Here's the code (ES6):
'use strict';
class ValidateFor {
constructor() {
this.restrict = 'A';
this.require = 'ngModel';
this.link = ($scope, $element, $attrs, ngModel) => {
var form = $scope[$attrs.validateFor];
var field = form[$element.attr('name')];
$scope.$on('form-submitted', () => {
this.checkForErrors(field, $element);
});
$scope.$watch(() => ngModel.$modelValue, () => {
if (field.$touched) {
this.checkForErrors(field, $element);
}
});
$element.bind('blur', () => {
this.checkForErrors(field, $element);
});
};
}
checkForErrors(field, $element) {
if (field.$invalid) {
$element.addClass('error');
} else {
$element.removeClass('error');
}
}
}
ValidateFor.$inject = [];
You could probably even eliminate the necessity for supplying the form name in validate-for. I just did it that way because I have some nested form situations.
valdr looks great. I haven't used it yet, but I will try it, and will update this post later.

Angular phone number directive using intl-tel-input lib

I am currently trying to create a custom directive that would initialize an input with the following lib : intl-tel-input .
So I downloaded the required .js file with bower:
<script src="bower_components/intl-tel-input/build/js/intlTelInput.min.js"></script>
Then I create my input :
input type="tel" class="form-control" id="tel" name="tel" ng-model="informations.tel" ng-keyup="checkPhoneFormat()" ng-click="checkPhoneFormat()">
And I'm initializing it at the beginning of my controller like this :
angular.element('#tel).intlTelInput({
validationScript: "../../bower_components/intl-tel-input/lib/libphonenumber/build/isValidNumber.js",
preferredCountries: ['en', 'fr']
});
My problem is that when I'm trying to access the informations.telmodel, it is always undefined. It seems that the input do not update the model value on the fly.
So I have to write something like this to bind the actual value of my input field with my non updated model value :
$scope.checkPhoneFormat = function(){
$scope.informations.telephone = angular.element('#telephone').val();
...}
It could be ok but I would like to create a custom directive to initialize such inputs, something like :
app.directive('phoneInput', function (PhoneFactory) {
return {
require: 'ngModel',
restrict: 'A',
scope: {
phoneNumber: '='
},
link: function (scope, element, attrs, ctrl) {
element.intlTelInput({
validationScript: "../../bower_components/intl-tel-input/lib/libphonenumber/build/isValidNumber.js",
preferredCountries: ['en', 'fr']
});
ctrl.$parsers.unshift(function(viewValue) {
console.log(viewValue);
});
}
};
});
But as ngModel is undefined the initialization function is never reached... Do you have any idea how I could solve my problem?
You are right that the model does not get updated automatically.
You could make a directive like this
app.directive('intlTel', function(){
return{
replace:true,
restrict: 'E',
require: 'ngModel',
template: '<input type="text" placeholder="e.g. +1 702 123 4567">',
link: function(scope,element,attrs,ngModel){
var read = function() {
var inputValue = element.val();
ngModel.$setViewValue(inputValue);
}
element.intlTelInput({
defaultCountry:'fr',
});
element.on('focus blur keyup change', function() {
scope.$apply(read);
});
read();
}
}
});
that could be called like this
<intl-tel ng-model="model.telnr"></intl-tel>
Here is a Plunker
There is a new directive called international-phone-number # https://github.com/mareczek/international-phone-number
Please checkout, any contributions are welcome
A few of us used Marks's directive, but the lack of testing and jquery's required position in the head was causing issues so ng-intl-tel-input was created:
https://github.com/hodgepodgers/ng-intl-tel-input
Check it out, it's unit and functionally tested with protractor
Play with it here:
http://hodgepodgers.github.io/ng-intl-tel-input/
Mark's directive worked for me:
https://github.com/mareczek/international-phone-number
One issue with intl-tel-input (at least with v3.6) is that it doesn't format the phone number you initialize it with properly* unless you have a plus sign ('+') before it. This leads to funky behavior for my users. I store my phone numbers normalized (without the plus sign) in my database so I need a hack to get around this. Instead of formatting on the server, I opted to format the phone number on the front-end. I added the following to Mark's directive to get the behavior I needed:
var makeSureInitialValueStartsWithPlusSign = function() {
var clear_watcher = scope.$watch(attrs.ngModel, function(changes) {
elem_val = element.val();
if (elem_val && elem_val[0] != "+") {
element.val("+" + changes);
clear_watcher();
}
});
};
makeSureInitialValueStartsWithPlusSign();
Thanks Mark
*By properly I mean converting '19734566789' to '+1 973-456-6789'. int-tel-input converts '19734566789' to '1 973-456-6789' (without the pus). When a user goes to edit it, they experience whacky behavior because the plus is not there.

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