Custom form validation directive to compare two fields
Solution 1
Many ways to skin a cat.
app.directive('lowerThan', [
function() {
var link = function($scope, $element, $attrs, ctrl) {
var validate = function(viewValue) {
var comparisonModel = $attrs.lowerThan;
if(!viewValue || !comparisonModel){
// It's valid because we have nothing to compare against
ctrl.$setValidity('lowerThan', true);
}
// It's valid if model is lower than the model we're comparing against
ctrl.$setValidity('lowerThan', parseInt(viewValue, 10) < parseInt(comparisonModel, 10) );
return viewValue;
};
ctrl.$parsers.unshift(validate);
ctrl.$formatters.push(validate);
$attrs.$observe('lowerThan', function(comparisonModel){
// Whenever the comparison model changes we'll re-validate
return validate(ctrl.$viewValue);
});
};
return {
require: 'ngModel',
link: link
};
}
]);
Usage:
<input name="min" type="number" ng-model="field.min" lower-than="{{field.max}}" />
<span class="error" ng-show="form.min.$error.lowerThan">
Min cannot exceed max.
</span>
Solution 2
You do not need any directive. Just assign the "min" value of max to min-value. Like:
<input name="min" type="number" ng-model="field.min"/>
<input name="max" type="number" ng-model="field.max" min=" {{ field.min }}"/>
And you do not need any customization.
More: you can do min=" {{ field.min + 1}}"
Solution 3
Would a simple comparison suit you?
<small class="error" ng-show="field.min > field.max">
I think a directive would be an overkill if your case is just this. If you do not feel comfortable with the view containing application logic, you can export it in a function of the controller:
$scope.isMinMaxInalid = function() {
return $scope.field.min > $scope.field.max;
};
And the template:
<small class="error" ng-show="isMinMaxInalid()">
Solution 4
For me, beyond a feedback message, I needed define the field as invalid, preventing submit. So I gathered some approaches, like @thestewie approach, with a view configuration to gather a solution for dates comparison. I hope can aggregate the solutions that were presented.
The code is in PLUNKER
angular.module('MyApp')
.directive('thisEarlierThan', function () {
return {
require: 'ngModel',
restrict: 'A',
link: function (scope, elem, attrs, ctrl) {
var startDate,
endDate;
scope.$watch(attrs.ngModel, function (newVal, oldVal, scope) {
startDate = newVal;
check();
});
scope.$watch(attrs.thisEarlierThan, function (newVal, oldVal, scope) {
endDate = newVal;
check();
});
var check = function () {
if (typeof startDate === 'undefined' || typeof endDate === 'undefined') {
return;
}
if (!validate(startDate)) {
startDate = new Date(startDate);
if (!validate(startDate)) {
return;
}
}
if (!validate(endDate)) {
endDate = new Date(endDate);
if (!validate(endDate)) {
return;
}
}
if (startDate < endDate) {
ctrl.$setValidity('thisEarlierThan', true);
}
else {
ctrl.$setValidity('thisEarlierThan', false);
}
return;
};
var validate = function (date) {
if (Object.prototype.toString.call(date) === '[object Date]') {
if (isNaN(date.getTime())) {
return false;
}
else {
return true;
}
}
else {
return false;
}
};
}
};
})
;
Solution 5
My version of the directive:
module.directive('greaterThan', function () {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attributes, ngModelController) {
var otherValue;
scope.$watch(attributes.greaterThan, function (value) {
otherValue = value;
ngModelController.$validate();
});
ngModelController.$parsers.unshift(function (viewValue) {
ngModelController.$setValidity('greaterThan', !viewValue || !otherValue || viewValue > otherValue);
return viewValue;
});
}
};
});
asfallows
If you see the name 'kaldrenon' somewhere on the internet, odds are good it's me!
Updated on July 09, 2022Comments
-
asfallows almost 2 years
I'm an angular newbie, and I'm stumbling over something in how angular's form validation directives work.
I know that I can fairly easily add directives to individual fields, but I'm trying to add a validation which will compare two form fields (both of which are elements of a model).
Here's a form skeleton:
<form name="edit_form" > <input name="min" type="number" ng-model="field.min"/> <input name="max" type="number" ng-model="field.max"/> </form> <div class="error" ng-show="edit_form.min.$dirty || edit_form.max.$dirty"> <small class="error" ng-show="(what goes here?)"> Min cannot exceed max </small> </div>
In short, I want to write a directive and use it to show/hide this
small.error
ifmin
andmax
both have values butmin > max
. How can I access both fields inside one directive? Is a directive the right tool for this job? -
asfallows over 10 yearsThanks for the quick reply Nikos. I do see that a comparison like that is much easier in a basic case. However, I'm interested in creating a more generalized form validation suite for a larger application. I'd like, ideally, to make a directive that I can apply to any form which has two fields that need to be compared. I'll let go of that if need be, but I'm hopeful about it.
-
Nikos Paraskevopoulos over 10 yearsI thought so... actually I have implemented a validation library for my current employer, closed source unfortunately. The concept of my library is to sort of "annotate" the model classes with the validation constraints (see Java beans validation) and enforce them in the view, keeping track of cross-field validation issues like this. So it is certainly doable, but it's rather big-ish...
-
asfallows over 10 yearsThanks for this tip! PLaying around with the plunker, I found that it seems to only be comparing the first digit in each field. Try putting 300 in max and 40 in min. Why is that?
-
Stewie over 10 yearsUps, forgot to parse the value to int. Plunker fixed. Anyway, I'd call this an answer, not a tip. ;)
-
asfallows over 10 yearsTHanks very much! Forgive my casual language, this is a good answer. :)
-
Priya about 10 yearsis it possible to use the same for date comparison also?
-
Stewie about 10 yearsCertainly is. Take my example, adjust it for date comparison and see how it goes. If problems then post on SO.
-
Priya about 10 yearsi have parsed date and compared them and it works perfectly but when page is loaded first time it displayes min can not exceed max. I tried to use ctrl.$setValidity('lowerThan', false); but still error is displayed first time
-
Stewie about 10 yearsPost new SO question and link it here.
-
Priya about 10 years
-
Fred over 9 yearsThanks for posting! I used your method to create my own. If you're interested, I have it here... gist.github.com/deanofharvard/2db49ff36d1445eb6c63
-
Guillaume Filion about 9 yearsThat's a brilliant solution. Much better than using a Directive. Up vote this answer please, this needs to be on top!
-
jcrowson about 9 yearsMuch better solution than the accepted answer, for simple situation.
-
Drumnbass about 8 yearsAlthought this solution is much better, the accepted one fits better the question, which's asking for a directive to validate 2 fields. I reach this post because my search matched the question and the solution given fits my problem!
-
greenoldman almost 8 yearsThank you! Is it possible to avoid evaluation in html and replace
lower-than="{{field.max}}"
withlower-than="field.max"
? In such casecomparisonModel
becomes just the name of the model binding in JS code -- how I could get the value of it then? -
rekam almost 8 yearsNice job to show how to create complex validators, but that's true too that kamirru answer goes straight to the point and is more easy to use the $error to show it (formname.fieldname.$error...)
-
Artisan over 7 yearsBrillance, this was a tremendous help and very neat to boot.