create custom directive in angularjs - angularjs

I have done it correctly by taking reference fro, official docs of angularjs.
But in directive's link method, when I set the validity using $setValidity() method, it does not reflects in view part using {{formname.controlname.$error.validationErrorKey}}
Please help me to track the error, or mistake that I am doing.
Thanks in advance
<form name="form" novalidate>
URL <input type="text" ng-model="myURL" name="myURL" my-url /> {{form.myURL.$error.myUrlError}}
<span class="errorMessage" ng-show="form.myURL.$dirty && form.myURL.$error.myUrlError">
please enter correct url
</span>
</form>
validationModule.directive("myUrl", function($window) {
//return Directive Definition Object (DDO)
return{
restrict:"A",
require: 'ngModel',
link: function(scope, elm, attrs, ctrl) {
elm.bind('blur',function() {
if (ctrl.$isEmpty(ctrl.$viewValue)) {
console.log('isEMpty-' + new Date());
ctrl.$setValidity("myUrlError", true);
} else {
var URL_REGEXP= /https?:\/\/(?:www\.|(?!www))[^\s\.]+\.[^\s]{2,}|www\.[^\s]+\.[^\s]{2,}/;
if (URL_REGEXP.test(ctrl.$viewValue)) {
console.log("valid-" + new Date());
ctrl.$setValidity("myUrlError", true);
} else {
console.log("invalid-" + new Date());
ctrl.$setValidity("myUrlError", false);
}
}
}); //end if 'blur' event listener
}//end of link function
};//end of DDO
});

use
scope.$apply( attrs.my-url);
inside your blur event i.e
element.bind('blur', function () {
scope.$apply( attrs.attrs.my-url );
}
});

Here is the problem:
elm.bind('blur',function(){
Angular know nothing about jquery events. They do not rise digest run. So you have to run it manualy by $scope.$applyAsync()
Here you can read more about applyAsync

In your sample you check if input is empty and if correspond to a pattern. I think your approach is so difficult for this feature.
I recommend to you to see attribute ng-required and ng-pattern to do that. It's a more simple way i think
Here a plunker to illustrate it : https://plnkr.co/edit/pRqXfsjduvQGuRwDBuUL?p=preview
URL <input type="text" ng-model="myURL" name="myURL" required="true" ng-pattern="/https?:\/\/(?:www\.|(?!www))[^\s\.]+\.[^\s]{2,}|www\.[^\s]+\.[^\s]{2,}/" /> {{form.myURL.$error}}
Another way is to create a custom validator : https://plnkr.co/edit/1hFMlB2IzuWpV7v8TOUP?p=preview
ctrl.$validators.myUrlValidator = function(modelValue, viewValue) {
console.log('vbalidate')
if(!viewValue || viewValue == "") {
return false;
}
if(!URL_REGEXP.test(viewValue)){
return false;
}
return true
}

Related

How can I extend AngularJS model flags on an Angular form?

AngularJS provides what they call 'model flags' on their forms. For example, you can have formName.$dirty, formName.$invalid, etc. What I want to know is how can I create my own custom flag for my AngularJS forms? A high level demonstration or link to an article would be a sufficient answer.
See here: how-to-add-custom-validation-to-an-angular-js-form.
In short, this is a custom valitation directive example:
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 this it's an example of it's 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>
But, again, read the referenced question and accepted answer, it's by far more complete...

Custom form validation in AngularJS

I have created one custom directive to validate the "domain" name form field.
form field element as
<input type="text" autofocus name="domain" ng-model="user.domain" domain-validate="/^[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$/" >
custom directive code is
app.directive('domainValidate', function() {
return {
// element must have ng-model attribute.
require: 'ngModel',
// scope = the parent scope
// elem = the element the directive is on
// attr = a dictionary of attributes on the element
// ctrl = the controller for ngModel.
link: function(scope, elem, attr, ctrl) {
//get the regex flags from the regex-validate-flags="" attribute (optional)
var flags = attr.domainValidateFlags || 'i';
// create the regex obj.
var regex = new RegExp(attr.domainValidate, flags);
// add a parser that will process each time the value is
// parsed into the model when the user updates it.
ctrl.$parsers.unshift(function(value) {
// test and set the validity after update.
var valid = regex.test(value);
ctrl.$setValidity('domainValidate', valid);
// if it's valid, return the value to the model,
// otherwise return undefined.
return valid ? value : undefined;
});
// add a formatter that will process each time the value
// is updated on the DOM element.
ctrl.$formatters.unshift(function(value) {
// validate.
console.log(value)
ctrl.$setValidity('domainValidate', regex.test(value));
// return the value or nothing will be written to the DOM.
return value;
});
}
};
});
I want to validate the field on form submit as well on value change.
The above code is not working, please can any one help me what is the error in above code or
let me know how to validate the domain name field
thanks
oldish post and i'm noob at angular, but try this
<form name="userForm" ng-submit="submit()" novalidate>
<div>
<input type="text" autofocus name="domain" ng-model="user.domain" domain-validate="^([a-zA-Z0-9]([a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,63}$" required>
<div ng-show="userForm.domain.$error.domainValidate">invalid domain</div>
<div ng-show="userForm.domain.$dirty && userForm.domain.$error.required">required</div>
</div>
<button type="submit">Submit</button>
</form>
then js directive
app.directive('domainValidate', function () {
return {
require: 'ngModel',
link: function (scope, elem, attr, ctrl) {
//get the regex flags from the regex-validate-flags="" attribute (optional)
var flags = attr.domainValidateFlags || 'i';
// create the regex obj.
var regex = new RegExp(attr.domainValidate, flags);
function setValidity(value) {
ctrl.$setValidity('domainValidate', regex.test(value));
}
scope.$watch(attr.ngModel, function (newValue, oldValue) {
if (newValue !== undefined && newValue !== oldValue) {
setValidity(newValue);
}
});
scope.submit = function () {
setValidity(ctrl.$modelValue);
for (var error in ctrl.$error) {
if (ctrl.$error[error]) return;
}
// send stuff
}
}
};
});
My suggestion is: Instead of custom directive validation use default URL validation:
<input type="url" autofocus name="domain" ng-model="user.domain">
Or if you don't want to use it, share your code in fiddle.

AngularJS directives: ng-click is not triggered after blur

DEMO
Consider the following example:
<input type="text" ng-model="client.phoneNumber" phone-number>
<button ng-click="doSomething()">Do Something</button>
.directive("phoneNumber", function($compile) {
return {
restrict: 'A',
scope: true,
link: function(scope, element, attrs) {
scope.mobileNumberIsValid = true;
var errorTemplate = "<span ng-show='!mobileNumberIsValid'>Error</span>";
element.after($compile(errorTemplate)(scope)).on('blur', function() {
scope.$apply(function() {
scope.mobileNumberIsValid = /^\d*$/.test(element.val());
});
});
}
};
});
Looking at the demo, if you add say 'a' at the end of the phone number, and click the button, doSomething() is not called. If you click the button again, then doSomething() is called.
Why doSomething() is not called for the first time? Any ideas how to fix this?
Note: It is important to keep the validation on blur.
Explain
Use click button, mousedown event is triggered on button element.
Input is on blur, blur callback triggered to validate input value.
If invalid, error span is displayed, pushing button tag down, thus cursor left button area. If user release mouse, mouseup event is not triggered. This acts like click on a button but move outside of it before releasing mouse to cancel the button click. This is the reason ng-click is not triggered. Because mouseup event is not triggered on button.
Solution
Use ng-pattern to dynamically validate the input value, and show/hide error span immediately according to ngModel.$invalid property.
Demo 1 http://jsbin.com/epEBECAy/14/edit
----- Update 1 -----
According to author's request, updated answer with another solution.
Demo 2 http://jsbin.com/epEBECAy/21/edit?html,js
HTML
<body ng-app="Demo" ng-controller="DemoCtrl as demoCtrl">
<pre>{{ client | json }}</pre>
<div id="wrapper">
<input type="text" phone-number
ng-model="client.phoneNumber"
ng-blur="demoCtrl.validateInput(client.phoneNumber)">
</div>
<button ng-mousedown="demoCtrl.pauseValidation()"
ng-mouseup="demoCtrl.resumeValidation()"
ng-click="doSomething()">Do Something</button>
</body>
Logic
I used ng-blur directive on input to trigger validation. If ng-mousedown is triggered before ng-blur, ng-blur callback will be deferred until ng-mouseup is fired. This is accomplished by utilizing $q service.
Here: http://jsbin.com/epEBECAy/25/edit
As explained by other answers, the button is moved by the appearance of the span before an onmouseup event on the button occurs, thus causing the issue you are experiencing. The easiest way to accomplish what you want is to style the form in such a way that the appearance of the span does not cause the button to move (this is a good idea in general from a UX perspective). You can do this by wrapping the input element in a div with a style of white-space:nowrap. As long as there is enough horizontal space for the span, the button will not move and the ng-click event will work as expected.
<div id="wrapper">
<div style='white-space:nowrap;'>
<input type="text" ng-model="client.phoneNumber" phone-number>
</div>
<button ng-click="doSomething()">Do Something</button>
</div>
It is because the directive is inserting the <span>Error</span> underneath where the button is currently placed, interfering with the click event location. You can see this by moving the button above the text box, and everything should work fine.
EDIT:
If you really must have the error in the same position, and solve the issue without creating your own click directive, you can use ng-mousedown instead of ng-click. This will trigger the click code before handling the blur event.
Not a direct answer, but a suggestion for writing the directive differently (the html is the same):
http://jsbin.com/OTELeFe/1/
angular.module("Demo", [])
.controller("DemoCtrl", function($scope) {
$scope.client = {
phoneNumber: '0451785986'
};
$scope.doSomething = function() {
console.log('Doing...');
};
})
.directive("phoneNumber", function($compile) {
var errorTemplate = "<span ng-show='!mobileNumberIsValid'> Error </span>";
var link = function(scope, element, attrs) {
$compile(element.find("span"))(scope);
scope.mobileNumberIsValid = true;
scope.$watch('ngModel', function(v){
scope.mobileNumberIsValid = /^\d*$/.test(v);
});
};
var compile = function(element, attrs){
var h = element[0].outerHTML;
var newHtml = [
'<div>',
h.replace('phone-number', ''),
errorTemplate,
'</div>'
].join("\n");
element.replaceWith(newHtml);
return link;
};
return {
scope: {
ngModel: '='
},
compile: compile
};
});
I would suggest using $parsers and $setValidity way while validating phone number.
app.directive('phoneNumber', function () {
return {
restrict: 'A',
require: '?ngModel',
link: function(scope, element, attrs, ctrl) {
if (!ctrl) return;
ctrl.$parsers.unshift(function(viewValue) {
var valid = /^\d*$/.test(viewValue);
ctrl.$setValidity('phoneNumber', valid);
return viewValue;
});
ctrl.$formatters.unshift(function(modelValue) {
var valid = /^\d*$/.test(modelValue);
ctrl.$setValidity('phoneNumber', valid);
return modelValue;
});
}
}
});
So, you will be able to use $valid property on a field in your view:
<form name="form" ng-submit="doSomething()" novalidate>
<input type="text" name="phone" ng-model="phoneNumber" phone-number/>
<p ng-show="form.phone.$invalid">(Show on error)Wrong phone number</p>
</form>
If you want to show errors only on blur you can use (found here: AngularJS Forms - Validate Fields After User Has Left Field):
var focusDirective = function () {
return {
restrict: 'E',
require: '?ngModel',
link: function (scope, element, attrs, ctrl) {
var elm = $(element);
if (!ctrl) return;
elm.on('focus', function () {
elm.addClass('has-focus');
ctrl.$hasFocus = true;
if(!scope.$$phase) scope.$digest();
});
elm.on('blur', function () {
elm.removeClass('has-focus');
elm.addClass('has-visited');
ctrl.$hasFocus = false;
ctrl.$hasVisited = true;
if(!scope.$$phase) scope.$digest();
});
elm.closest('form').on('submit', function () {
elm.addClass('has-visited');
ctrl.$hasFocus = false;
ctrl.$hasVisited = true;
if(!scope.$$phase) scope.$digest();
})
}
}
};
app.directive('input', focusDirective);
So, you will have hasFocus property if field is focused now and hasVisited property if that field blured one or more times:
<form name="form" ng-submit="doSomething()" novalidate>
<input type="text" name="phone" ng-model="phoneNumber" phone-number/>
<p ng-show="form.phone.$invalid">[error] Wrong phone number</p>
<p ng-show="form.phone.$invalid
&& form.phone.$hasVisited">[error && visited] Wrong phone number</p>
<p ng-show="form.phone.$invalid
&& !form.phone.$hasFocus">[error && blur] Wrong phone number</p>
<div><input type="submit" value="Submit"/></div>
</form>
Demo: http://jsfiddle.net/zVpWh/4/
I fixed it with the following.
<button class="submitButton form-control" type="submit" ng-mousedown="this.form.submit();" >

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.

Angularjs: input[text] ngChange fires while the value is changing

ngChange is firing while the value is changing (ngChange are not similiar to the classic onChange event). How can i bind the classic onChange event with angularjs, that will only fire when the contents are commited?
Current binding:
<input type="text" ng-model="name" ng-change="update()" />
This post shows an example of a directive that delays the model changes to an input until the blur event fires.
Here is a fiddle that shows the ng-change working with the new ng-model-on-blur directive. Note this is a slight tweak to the original fiddle.
If you add the directive to your code you would change your binding to this:
<input type="text" ng-model="name" ng-model-onblur ng-change="update()" />
Here is the directive:
// override the default input to update on blur
angular.module('app', []).directive('ngModelOnblur', function() {
return {
restrict: 'A',
require: 'ngModel',
priority: 1, // needed for angular 1.2.x
link: function(scope, elm, attr, ngModelCtrl) {
if (attr.type === 'radio' || attr.type === 'checkbox') return;
elm.unbind('input').unbind('keydown').unbind('change');
elm.bind('blur', function() {
scope.$apply(function() {
ngModelCtrl.$setViewValue(elm.val());
});
});
}
};
});
Note: as #wjin mentions in the comments below this feature is supported directly in Angular 1.3 (currently in beta) via ngModelOptions. See the docs for more info.
This is about recent additions to AngularJS, to serve as future answer (also for another question).
Angular newer versions (now in 1.3 beta), AngularJS natively supports this option, using ngModelOptions, like
ng-model-options="{ updateOn: 'default blur', debounce: { default: 500, blur: 0 } }"
NgModelOptions docs
Example:
<input type="text" name="username"
ng-model="user.name"
ng-model-options="{updateOn: 'default blur', debounce: {default: 500, blur: 0} }" />
In case anyone else looking for additional "enter" keypress support, here's an update to the fiddle provided by Gloppy
Code for keypress binding:
elm.bind("keydown keypress", function(event) {
if (event.which === 13) {
scope.$apply(function() {
ngModelCtrl.$setViewValue(elm.val());
});
}
});
For anyone struggling with IE8 (it doesn't like the unbind('input'), I found a way around it.
Inject $sniffer into your directive and use:
if($sniffer.hasEvent('input')){
elm.unbind('input');
}
IE8 calms down if you do this :)
According to my knowledge we should use ng-change with the select option and in textbox case we should use ng-blur.
Isn't using $scope.$watch to reflect the changes of scope variable better?
Override the default input onChange behavior (call the function only
when control loss focus and value was change)
NOTE: ngChange is not similar to the classic onChange event it firing
the event while the value is changing This directive stores the value
of the element when it gets the focus
On blurs it checks whether the
new value has changed and if so it fires the event
#param {String} - function name to be invoke when the "onChange"
should be fired
#example < input my-on-change="myFunc" ng-model="model">
angular.module('app', []).directive('myOnChange', function () {
return {
restrict: 'A',
require: 'ngModel',
scope: {
myOnChange: '='
},
link: function (scope, elm, attr) {
if (attr.type === 'radio' || attr.type === 'checkbox') {
return;
}
// store value when get focus
elm.bind('focus', function () {
scope.value = elm.val();
});
// execute the event when loose focus and value was change
elm.bind('blur', function () {
var currentValue = elm.val();
if (scope.value !== currentValue) {
if (scope.myOnChange) {
scope.myOnChange();
}
}
});
}
};
});
I had exactly the same problem and this worked for me. Add ng-model-update and ng-keyup and you're good to go! Here is the docs
<input type="text" name="userName"
ng-model="user.name"
ng-change="update()"
ng-model-options="{ updateOn: 'blur' }"
ng-keyup="cancel($event)" />

Resources