Custom form validation directive to compare two fields

51,661

Solution 1

Many ways to skin a cat.

PLUNKER

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;
            });
        }
    };
});
Share:
51,661
asfallows
Author by

asfallows

If you see the name 'kaldrenon' somewhere on the internet, odds are good it's me!

Updated on July 09, 2022

Comments

  • asfallows
    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 if min and max both have values but min > max. How can I access both fields inside one directive? Is a directive the right tool for this job?

  • asfallows
    asfallows over 10 years
    Thanks 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
    Nikos Paraskevopoulos over 10 years
    I 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
    asfallows over 10 years
    Thanks 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
    Stewie over 10 years
    Ups, forgot to parse the value to int. Plunker fixed. Anyway, I'd call this an answer, not a tip. ;)
  • asfallows
    asfallows over 10 years
    THanks very much! Forgive my casual language, this is a good answer. :)
  • Priya
    Priya about 10 years
    is it possible to use the same for date comparison also?
  • Stewie
    Stewie about 10 years
    Certainly is. Take my example, adjust it for date comparison and see how it goes. If problems then post on SO.
  • Priya
    Priya about 10 years
    i 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
    Stewie about 10 years
    Post new SO question and link it here.
  • Priya
    Priya about 10 years
  • Fred
    Fred over 9 years
    Thanks 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
    Guillaume Filion about 9 years
    That's a brilliant solution. Much better than using a Directive. Up vote this answer please, this needs to be on top!
  • jcrowson
    jcrowson about 9 years
    Much better solution than the accepted answer, for simple situation.
  • Drumnbass
    Drumnbass about 8 years
    Althought 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
    greenoldman almost 8 years
    Thank you! Is it possible to avoid evaluation in html and replace lower-than="{{field.max}}" with lower-than="field.max"? In such case comparisonModel becomes just the name of the model binding in JS code -- how I could get the value of it then?
  • rekam
    rekam almost 8 years
    Nice 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
    Artisan over 7 years
    Brillance, this was a tremendous help and very neat to boot.