scope in directive breaks view scope with async validation - angularjs

I am following a Year of Moo tutorial on async form validations and ngMessages (I'm using 1.3.0-beta.14 so I can't use the actual $async validator).
My validation is working, but the scope in the view is non-existent! On form submission, there is no username value and adding the appropriate {{username}} binding elsewhere in the view never returns a value. However, the console Log at the end of my directive does return the correct value, it just never transfers to the view?
Here is the directive, basically lifted from the article (the console.log before the final return does log the correct value from the view):
.directive('recordAvailabilityValidator', function($http) {
return {
require : 'ngModel',
link : function(scope, element, attrs, ngModel) {
var apiUrl = attrs.recordAvailabilityValidator;
function setAsLoading(bool) {
ngModel.$setValidity('recordLoading', !bool);
}
function setAsAvailable(bool) {
ngModel.$setValidity('recordAvailable', bool);
}
ngModel.$parsers.push(function(value) {
if(!value || value.length == 0) return;
setAsLoading(true);
setAsAvailable(false);
$http.get(apiUrl, { params: {attr : value }} )
.success(function(response) {
setAsLoading(false);
setAsAvailable(true);
})
.error(function() {
setAsLoading(false);
setAsAvailable(false);
});
console.log(value)
return value;
})
}
}
});
Here is the relevant part of the html template:
<p>
<label>Username</label>
<input type="text" ng-model="signup.username" class='form-control' name='username'
require minlength='3' record-availability-validator="/api/v1/validations/username"
ng-model-options="{ debounce : { 'default' : 500, blur : 0 } }">
</p>
<div ng-messages="signupForm.username.$error">
<div ng-message="required">You did not enter a username</div>
<div ng-message="minlength">Username must be at least 4 characters</div>
<div ng-message="recordLoading">Checking database...</div>
<div ng-message="recordAvailable">The username is already in use...</div>
</div>
{{signup.username}}
The debug {{signup.username}} never shows a value. If I change it to just {{signup}} the other values show fine. Also, if I add this directive to another input, like email the same strange behavior is there. I googled around and tried added scope: true to my directive, but nothing happened.

Related

AngularJS trigger field validation after form loads

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

asyncValidator dependent on other field's value

I'm trying to add a validation field to one of my inputs, it should request the server whether the inputted VAT number is valid, so I'm using an async validator for this. Works fine with this code:
myApp.factory('isValidVat', function($q, $http) {
return function(vat) {
var deferred = $q.defer();
console.log(vat);
$http.get('/api/vat/' + vat).then(function() {
deferred.resolve();
}, function() {
deferred.reject();
});
return deferred.promise;
}
});
myApp.directive('validVat', function(isValidVat) {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
ngModel.$asyncValidators.vat = isValidVat;
}
};
});
<form name="form" novalidate ng-submit="check(form)">
<div class="vat-field">
<label>VAT number
<input type="text" ng-model="formModel.vat" name="vat" valid-vat="true">
</label>
<div ng-show="registerForm.$submitted || registerForm.vat.$touched">
<span ng-show="registerForm.vat.$error.vat">
<small class="error">Your VAT address is not valid, please correct.</small>
</span>
</div>
</div>
<div class="country-field">
<label>Country
<select ng-model="formModel.country" name="country">
<option value="{{country.iso_3}}" ng-repeat="country in countries()">{{country.name}}</option>
</select>
</label>
</div>
<button type="submit">Check</button>
</form>
However, I want to make this asyncValidator check conditional on the value of another field (country, more specifically whether the country is a EU country).
The country field is a combobox, populoated via a service which has a record of all countries and their vat information and eu status.
However, I do not know how to inject the value of the selected country into the factory function. One idea was to link the selected country to it's own service and use it from the factory, but then the validation doesn't run again when another country is selected. If a non-EU country is selected, I don't really care what is in the field.
Pasing value into derective is easy, you can pass it to same attribute or some other. Its better not to use isolated scope, because attribute directive should be able to work on every element. So You can pass your country information to the validation directive, and then pass it to function which is returned by factory.
The other thing is dependency. Validation is only run when model itself is changed. So you have to place watch / observation on it.
Stop talking ... its code time ...
Here is fully working example based on your code :
http://jsbin.com/yuqibajehe/edit?html,js,output
.factory('isValidVat', function($q, $http, $timeout) {
return function(vat, country) {
var deferred = $q.defer();
console.log(vat, country);
$timeout(function() {
if (vat === country) {
deferred.resolve();
}
deferred.reject();
},1000);
return deferred.promise;
};
})
.directive('validVat', function(isValidVat) {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
attrs.$observe('validVat', function() {
ngModel.$validate();
});
ngModel.$asyncValidators.vat = function(vat) {
return isValidVat(vat, attrs.validVat);
};
}
};
});
Ass you can see I have little bit simplified the async validator, but enough for now. function has two arguments, so I call it from other function.
Directive is used like that:
<input type="text" ng-model="vm.vat" name="vat" valid-vat="{{formModel.country}}" />
So we pass the value of model straight to the attribute valid-vat.
In directive we can then send the value into validator.
Then we have to observe the content of validator and run validation if this is changed using $validate().
For this case it is probably enough, but if you want to pass model directly like: valid-vat="formModel.country" it wouldn't work, because value of attribute doesn't change. So you would have go to the scope, or better evaluate attr value and watch its changes - like in this example: http://plnkr.co/edit/296x2shAVSe7FRv3mJnp?p=preview

AngularJS ng-model value is lost after custom validation directive is triggered

I created a custom validation directive and used it in a form. It can be triggered with no problem, but after the validation is triggered, I found that the model value is just lost. Say I have
ng-model="project.key"
and after validation, project.key doesn't exist in the scope anymore. I think somehow I understood AngularJS wrong and did something wrong.
Code speaks.
Here is my html page:
<div class="container">
...
<div class="form-group"
ng-class="{'has-error': form.key.$invalid && form.key.$dirty}">
<label for="key" class="col-sm-2 control-label">Key</label>
<div class="col-sm-10">
<input type="text" class="form-control text-uppercase" name="key"
ng-model="project.key" ng-model-options="{ debounce: 700 }"
placeholder="unique key used in url"
my-uniquekey="vcs.stream.isProjectKeyValid" required />
<div ng-messages="form.key.$error" ng-if="form.key.$dirty"
class="help-block">
<div ng-message="required">Project key is required.</div>
<div ng-message="loading">Checking if key is valid...</div>
<div ng-message="keyTaken">Project key already in use, please
use another one.</div>
</div>
</div>
</div>
<div class="col-sm-offset-5 col-sm-10">
<br> Cancel
<button ng-click="save()" ng-disabled="form.$invalid"
class="btn btn-primary">Save</button>
<button ng-click="destroy()" ng-show="project.$key"
class="btn btn-danger">Delete</button>
</div>
</form>
And here's my directive:
.directive('myUniquekey', function($http) {
return {
restrict : 'A',
require : 'ngModel',
link : function(scope, elem, attrs, ctrl) {
var requestTypeValue = attrs.myUniquekey;
ctrl.$parsers.unshift(function(viewValue) {
// if (viewValue == undefined || viewValue == null
// || viewValue == "") {
// ctrl.$setValidity('required', false);
// } else {
// ctrl.$setValidity('required', true);
// }
setAsLoading(true);
setAsValid(false);
$http.get('/prism-cmti/2.1', {
params : {
requestType : requestTypeValue,
projectKey : viewValue.toUpperCase()
}
}).success(function(data) {
var isValid = data.isValid;
if (isValid) {
setAsLoading(false);
setAsValid(true);
} else {
setAsLoading(false);
setAsValid(false);
}
});
return viewValue;
});
function setAsLoading(bool) {
ctrl.$setValidity('loading', !bool);
}
function setAsValid(bool) {
ctrl.$setValidity('keyTaken', bool);
}
}
};
});
Here's the controller for the form page:
angular.module('psm3App').controller(
'ProjectCreateCtrl',
[ '$scope', '$http', '$routeParams', '$location',
function($scope, $http, $routeParams, $location) {
$scope.save = function() {
$http.post('/prism-cmti/2.1', {requestType:'vcs.stream.addProject', project:$scope.project})
.success(function(data) {
$location.path("/");
});
};
}]);
Before this bug, somehow I need to handle the required validation in my custom validation directive too, if I don't do it, required validation would go wrong. Now I think of it, maybe the root cause of these two problems is the same: the model value is gone after my directive link function is triggered.
I'm using Angular1.3 Beta 18 BTW.
Any help is appreciated. Thanks in advance.
Update:
Followed #ClarkPan's answer, I updated my code to return viewValue in ctrl.$parsers.unshift() immediately, which makes required validation works well now, so I don't need lines below any more.
// if (viewValue == undefined || viewValue == null
// || viewValue == "") {
// ctrl.$setValidity('required', false);
// } else {
// ctrl.$setValidity('required', true);
// }
But the {{project.key}} still didn't get updated.
Then I tried to comment out these two lines here:
setAsLoading(true);
setAsValid(false);
Model value {{project.key}} got updated. I know that if any validation fails, the model value will be cleared, but I thought
function(data) {
var isValid = data.isValid;
if (isValid) {
setAsLoading(false);
setAsValid(true);
} else {
setAsLoading(false);
setAsValid(false);
}
}
in $http.get(...).success() should be executed in $digest cycle, which means the model value should be updated.
What is wrong?
This is happening because angular does not apply any change to the scope and $modelValue if there is any invalid flag set in the model. When you start the validation process, you are setting the 'keyTaken' validity flag to false. That is telling to angular not apply the value to the model. When the ajax response arrives and you set the 'keyTaken' validity flag to true, the $modelValue was already set to undefined and the property 'key' was gone. Try to keep all validity flags set to true during the ajax request. You must avoid the calls to setAsLoading(true) and setAsValid(false) before the ajax call and keep all validity flags set to true. Only after the ajax response set the validity flag.
NOTE:This answer below only applies if you're using angular versions prior to 1.3 (before they introduced the $validators concept).
From my reading of your myUniqueKey directive, you want to validate the projectkey asynchronously. If that is the case, that would be your problem. ngModel's $parser/$formatter system doesn't expect asynchronous calls.
The anonymous function you used in the $parsers array does not return a value, as $http is an asynchronous method that returns a method. You'll want to return the viewValue immediately from that method.
Then in the .success callback of your $http call, you can ten set the validity and loading status. I don't recommend you try to change the viewValue (unless that is not your purpose in returning either undefined or viewValue) at this point as it will probably trigger another run of the $parsers.
So:
ctrl.$parsers.unshift(function(viewValue){
//...omitted for clarity
$http.get(
//...
).success(function(data){
setAsLoading(false);
setAsValid(data.isValid);
});
//...
return viewValue;
});
If the value is not valid, by default the model will not be updated (as explained in the accepted answer), but you can make the model to be updated in any case by using allowInvalid in ng-model-options
For the input field in the question:
<input type="text" class="form-control text-uppercase" name="key"
ng-model="project.key" ng-model-options="{ debounce: 700,
allowInvalid: true }"
placeholder="unique key used in url"
my-uniquekey="vcs.stream.isProjectKeyValid" required />

Error: [ngModel:nonassign] Expression is non-assignable

Trying to display a columnvalue from a gridcollection based on another value in that same row.
The user can select/change values in a modal which contains a grid with values. When the modal closes the values are passed back. At that moment I would like to set a value for 'Also known as':
html:
Also known as: <input type="text" `ng-model="displayValue(displayNameData[0].show,displayNameData[0].value)">`
I created a function on scope to select the value only when the 'show' value is true:
$scope.displayValue = function (show, val) {
if (show) {
return val;
}
else {
return '';
}
}
However when I close the modal I get an error:
Error: [ngModel:nonassign] Expression 'displayValue(displayNameData[0].show,displayNameData[0].value)' is non-assignable.
plnkr reference:http://plnkr.co/edit/UoQHYwAxwdvX0qx7JFVW?p=preview
Using ng-value instead of ng-model worked for me.
As HackedByChinese mentioned, you can't bind ng-model to a function, so try like this:
<input type="text" ng-if="displayNameData[0].show"
ng-model="displayNameData[0].value">
Or if you want this control to be visible you can create directive, add function to $parsers that will set empty value according to show:
angular.module('yourModule').directive('bindIf', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
function parser(value) {
var show = scope.$eval(attrs.bindIf);
return show ? value: '';
}
ngModel.$parsers.push(parser);
}
};
});
HTML:
<input type="text" bind-if="displayNameData[0].show"
ng-model="displayNameData[0].value">
You can bind ng-model to function
Binding to a getter/setter
Sometimes it's helpful to bind ngModel to a
getter/setter function. A getter/setter is a function that returns a
representation of the model when called with zero arguments, and sets
the internal state of a model when called with an argument. It's
sometimes useful to use this for models that have an internal
representation that's different from what the model exposes to the
view.
index.html
<div ng-controller="ExampleController">
<form name="userForm">
<label>Name:
<input type="text" name="userName"
ng-model="user.name"
ng-model-options="{ getterSetter: true }" />
</label>
</form>
<pre>user.name = <span ng-bind="user.name()"></span></pre>
</div>
app.js
angular.module('getterSetterExample', [])
.controller('ExampleController', ['$scope', function($scope) {
var _name = 'Brian';
$scope.user = {
name: function(newName) {
// Note that newName can be undefined for two reasons:
// 1. Because it is called as a getter and thus called with no arguments
// 2. Because the property should actually be set to undefined. This happens e.g. if the
// input is invalid
return arguments.length ? (_name = newName) : _name;
}
};
}]);

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