I have a simple form with a directive:
<form name="testform">
<input name="testfield" type="text" ng-model="test.name" ng-minlength="3" my-validate>
</form>
Since Angular validates the field itself when entering less than 3 characters, it sets testform.testfield.$invalid.
What I want to achieve is to add some custom validation functionality in a directive and react to this angular validation/set the validation result e.g. with the help of watchers or events like so:
angular.module('myApp.directives').directive('myValidate', [function() {
return {
link: function(scope, elem, attrs) {
elem.on('keyup', function() {
// do some custom validation
// if valid, set elem to $valid
// else set $error
// something like elem.set('$valid');
}
}
};
}]);
How can I achieve the combination of HTML5, Angular and custom form validation while using Angulars testform.testfield.$invalid, testform.testfield.$valid, testform.testfield.$error etc. logic?
By default Angular provides some directives for input validation (like required, pattern, min, max etc.). If these directives doesn't suffice, custom validation is available. To be able to do that, the directive requires the model used for validation, then it can set the validity of the element like below:
.directive('myValidate', [ function() {
return {
require: 'ngModel',
link: function(scope, elem, attrs, ctrl) {
ctrl.$setValidity('myValidate', false);
}
};
}]);
A Plunker with such a directive which will become invalid when you enter '0' is HERE.
You can read more about custom validation on Angular forms documentation.
You can do custom validation and combine it with Angular's built in validation, you just need to require ngModel, pass ctrl as the 4th value to your link function, and use ctrl.$setValidity to indicate if your custom validation passes or fails.
The example below will be invalid if the length < 3 OR if the value is the string 'invalid':
var app = angular.module('app', []);
app.controller('controller', ['$scope',
function(scope) {
scope.test = {
name: ''
};
}
]);
app.directive('myValidate', [
function() {
return {
require: 'ngModel',
link: function(scope, elem, attrs, ctrl) {
ctrl.$parsers.unshift(function(value) {
var valid = true;
if (value === 'invalid') {
valid = false;
}
ctrl.$setValidity(attrs.name, valid);
return valid ? value : undefined;
});
}
};
}
]);
Markup:
<body ng-app="app">
<div ng-controller="controller">
<form name="testform">
<input name="testform" type="text" ng-model="test.name" ng-minlength="3" my-validate /><br/>
</form>
<span style="color:green" ng-show="testform.testform.$valid">** valid **</span>
<span style="color:red" ng-show="testform.testform.$invalid">** invalid **</span>
</div>
<script src="script.js"></script>
</body>
Here is a plunker with this code: http://plnkr.co/edit/6XIKYvGwMSh1zpLRbbEm
The Angular documentation talks about this, though it wasn't totally obvious: https://docs.angularjs.org/api/ng/type/ngModel.NgModelController
This blog helped me get a hold on this: http://www.benlesh.com/2012/12/angular-js-custom-validation-via.html
Related
I am using the currency filter shown in the "best answer" in this thread: Apply currency filter to the input field in angularjs
The problem is, I also need the input field to be numerical only, i.e. type="number". It works perfectly with type="text", but when I try to combine the two, the input disappears when the textbox loses focus.
<div class="table-cell">
<div class="field-label table-cell">Estimated Yearly<br />Savings</div>
<input ng-model="model.yearlySavings" type="number" format="currency">
</div>
JS:
mainModule.directive('format', ['$filter', function ($filter) {
return {
require: '?ngModel',
link: function (scope, elem, attrs, ctrl) {
if (!ctrl) return;
ctrl.$formatters.unshift(function (a) {
return $filter(attrs.format)(ctrl.$modelValue)
});
elem.bind('blur', function (event) {
var plainNumber = elem.val().replace(/[^\d|\-+|\.+]/g, '');
elem.val($filter(attrs.format)(plainNumber));
});
}
};
}]);
Note: I would have commented in that thread, but I don't have enough "rep" to leave a comment...
I ended up using a custom formatter from another SO thread: AngularJS - How to apply currency filter on textbox as I type?
The directive is called fcsa-number: https://github.com/FCSAmericaDev/angular-fcsa-number
It took some finesse, but I got it working like this:
<input ng-model="model.yearlySavings" name="ys" type="text"
ng-class="{ 'error': formName.ys.$invalid }" fcsa-number="{ prepend: '$', preventInvalidInput: true, maxDecimals: 2 }">
The "ng-class" bit is used to dynamically style the border to red when the element listed (in this case "yearlySavings" aka "ys") is invalid. In this example, it prepends my input with "$", it prevents invalid input from being entered at all (like regular text characters), and it sets the maximum decimals to 2. Oddly enough, it does not prevent 3+ decimals from being entered like I would expect (like the regular text characters), but it does invalidate the element with more than 2.
Create directive as below
HTML
<div ng-app="myApp">
<div ng-controller="myCtrl">
{{amount}}
<input format-to-currency amount="amount">
</div>
</div>
JS
angular.module('myApp', [])
.controller('myCtrl', function($scope) {
$scope.ampunt = 2;
})
.directive('formatToCurrency', function($filter) {
return {
scope: {
amount: '='
},
link: function(scope, el, attrs) {
el.val($filter('currency')(scope.amount));
el.bind('focus', function() {
el.val(scope.amount);
});
el.bind('input', function() {
scope.amount = el.val();
scope.$apply();
});
el.bind('blur', function() {
el.val($filter('currency')(scope.amount));
});
}
}
});
link http://jsfiddle.net/moL8ztrw/7/
I'm building an app in angular js.
I want to create a restriction an input field to accept only 3, 6, 9, 12, 15 and so on.
It should mark it as no valid if the user writes for example an 8.
I couldn't find how to do this, help would be appreciated.
You can create a directive and add your own validator that checks for multiples of 3.
See plunker
Markup:
<input name="numberField" type="number" data-ng-model="model.number" data-multiple-validator="3"/>
JS:
app.directive('multipleValidator', [function(){
return {
restrict: 'AC',
require: 'ngModel',
link: function(scope, elem, attr, ngModelCtrl) {
if (!ngModelCtrl) {
return;
}
var multiple = parseInt(attr.multipleValidator);
if (!multiple) {
return;
}
ngModelCtrl.$validators.multipleValidator = function(modelValue, viewValue) {
return !!modelValue && modelValue%multiple === 0;
}
}
}
}])
If it's only the numbers 3, 6, 9, 12 and 15 then you can just use ng-pattern like so
<input name="numberField" type="number" data-ng-model="model.number" data-ng-pattern="/^(3|6|9|12|15)$/"/>
EDIT
If your multiple value is dynamic then you will have to watch for changes in your directive using the very handy $observe and re-validate when it changes. See updated plunker.
Markup:
<input placeholder="input" name="numberField" type="number" data-ng-model="model.number" data-multiple-validator="{{model.multiple}}"/>
JS:
app.directive('multipleValidator', [function(){
return {
restrict: 'AC',
require: 'ngModel',
link: function(scope, elem, attr, ngModelCtrl) {
if (!ngModelCtrl) {
return;
}
ngModelCtrl.$validators.multipleValidator = function(modelValue, viewValue) {
var multiple = parseInt(attr.multipleValidator);
if (!multiple) {
// If there is no multiple then assume that it is valid
return true;
}
return !!modelValue && modelValue%multiple === 0;
}
// Watch for changes to the multipleValidator attribute
attr.$observe('multipleValidator', function(newVal, oldVal) {
// Re-validate the input when a change is detected
ngModelCtrl.$validate();
});
}
}
}]);
There is no need to use a custom directive. Angular has built in directive called ng-pattern which lets you match the input field to your given regular expression.
Here is how i have achieved this..
CODE...
<html ng-app="num">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
</head>
<body ng-controller="numCtrl">
<form class="digits" name="digits">
<input type="text" placeholder="digits here plz" name="nums" ng-model="nums" required ng-pattern="/^([0369]|[258][0369]*[147]|[147]([0369]|[147][0369]*[258])*[258]|[258][0369]*[258]([0369]|[147][0369]*[258])*[258]|[147]([0369]|[147][0369]*[258])*[147][0369]*[147]|[258][0369]*[258]([0369]|[147][0369]*[258])*[147][0369]*[147])*$/" />
<p class="alert" ng-show="digits.nums.$error.pattern">Multiples of three only accepted.</p>
<br>
</form>
<script>
var app = angular.module('num',[]);
app.controller('numCtrl', function($scope, $http){
$scope.digits = {};
});
</script>
</body>
</html>
See this jsfiddle for a working example https://jsfiddle.net/s7mmvxq5/5/
The approach I took was to create a directive that just adds an additional $validator to the form field. Since the validator is just a function you can do whatever check you'd like in it.
This section of the angular docs might be of interest https://docs.angularjs.org/guide/forms#custom-validation
I am using a directive to build a custom validator and it works fine. But, it was called only once! If my "roleItems" are updated, this directive was not called again! How can it be called every time when "roleItems" are updated?
Here are the markups. And "Not-empty" is my directive.
<form name="projectEditor">
<ul name="roles" ng-model="project.roleItems" not-empty>
<li ng-repeat="role in project.roleItems"><span>{{role.label}}</span> </li>
<span ng-show="projectEditor.roles.$error.notEmpty">At least one role!</span>
</ul>
</form>
This is my directive. It should check if the ng-model "roleItems" are empty.
angular.module("myApp", []).
directive('notEmpty', function () {
return {
require: 'ngModel',
link: function (scope, elm, attrs, ctrl) {
ctrl.$validators.notEmpty = function (modelValue, viewValue) {
if(!modelValue.length){
return false;
}
return true;
};
}
};
});
Main purpose of validator is validate ngModel value of user input or model change, so it should be uset to checkbox/textara/input and etc. You cant validate ng-model of everything. Angular is enough intelligent to knows that ng-model makes no sens so he is just ignoring it .
I you wanna change only error message you can check it via .length property. If you wanna make whole form invalid , i suggest you to make custom directive , put it on , and then in validator of this directive check scope.number.length > 0
Basically just adjust your directive code to input element and hide it .... via css or type=hidden, but dont make ngModel="value" its not make sense because ng-model is expecting value which can be binded and overwriteen but project.roleItems is not bindable! so put ng-model="dummyModel" and actual items to another param ...
<input type="hidden" ng-model="dummyIgnoredModel" number="project.roleItems" check-empty>
angular.module("myApp", []).
directive('checkEmpty', function () {
return {
require: 'ngModel',
link: function (scope, elm, attrs, ctrl) {
ctrl.$validators.push(function (modelValue, viewValue) {
if(!scope.number.length){
return false;
}
return true;
});
//now we must "touch" ngModel
scope.$watch(function()
{
return scope.number
}, function()
{
ctrl.$setViewValue(scope.number.length);
});
}
};
});
In angularjs 1.3.15 I have a custom validator which uses its attribute value:
sApp.directive('customValidator', [ '$q', function($q){
return {
require: 'ngModel',
link : function(scope, elm, attrs, ctrl){
ctrl.$asyncValidators.customValidator= function(modelValue)
{
// I need the value of "attrs.customValidator"
};
}
};
}]);
I use this validator in the following way:
<form ... ng-show="data">
<input ... custom-validator='{{data}}'/>
</form>
data is some data being asynchronously loaded by a web-service. Unfortunately when the validator is called the attribute value is not available yet (despite ng-show="data"). So i call scope.$apply() inside the validator. This solves my problem but I get the error "$digest already in progress".
Does anybody know a better solution?
I think a synchronous formatter/parser will suffice. Here is a plunker
Here is the directive:
app.directive('exactlyEquals', function() {
return {
require: "ngModel",
link: function(scope, elm, attrs, ctrl) {
var validator = function(value) {
ctrl.$setValidity('exactlyEquals', value == attrs.exactlyEquals);
return value;
};
ctrl.$parsers.unshift(validator);
ctrl.$formatters.unshift(validator);
}
};
});
And its usage:
<form name="theForm">
<input type="text" exactly-equals="test2" ng-model="data.name" />
</form>
{{ theForm.$valid }}
#user3632710 ng-if="data" did the job. My code looks now like this:
<form ng-controller="MyController">
<div ng-if="data"> <!-- ng-if cannot by on same DOM as controller -->
<input ... custom-validator='{{data}}'/>
</div>
</form>
I have a directive that links to a textbox on the form, and I would like for this directive to set the 'required' error.
Here's a fiddle that shows what I'm trying to do
http://jsfiddle.net/scottieslg/7qLsj3rr/3/
Html:
<div ng-app='myApp' ng-controller='TestCtrl'>
<ng-form name='testForm'>
<input type='text' name='myInput' />
<div ng-messages="testForm.myInput.$error">
<div ng-message="required">Required</div>
</div>
<test-directive ng-model='testModel'></test-directive>
</ng-form>
</div>
Javascript:
var app = angular.module('myApp', ['ngMessages']);
app.controller('TestCtrl', function($scope) {
$scope.testModel = {}
});
app.directive('testDirective', function() {
return {
restrict: 'E',
require: 'ngModel',
template: '<div><button ng-click="setError()">Set Error</button></div>',
link: function(scope, element, attrs, ngModelCtrl) {
scope.setError = function() {
// How can I set .setValidate('require', true) on myInput from here??
}
}
}
});
If you want the test-directive to be able to control the ngModelController instance on a separate named input in a form, then using the ng-model directive again isn't the right thing to do, as that would create a new ngModelController instance on test-directive.
What the test-directive actually needs to know is the name of the input which has the controller:
<test-directive name='myInput'></test-directive>
Then it can access the form controller, using
require: '^form',
and use the name attribute value to find the ngModelController instance on the form:
link: function(scope, element, attrs, formController) {
scope.setError = function() {
var ngModelCtrl = formController[attrs.name];
ngModelCtrl.$setValidity('required', false);
}
}
You can see this at http://jsfiddle.net/7qLsj3rr/6/ .
Note: if you're using required as the key, then as soon as you type in the input again again, angular's own required validation will kick in an remove the error.