42

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
  • 5,998
  • 6
  • 29
  • 48

6 Answers6

77

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}}"

kamil.rak
  • 1,378
  • 1
  • 14
  • 28
  • That's a brilliant solution. Much better than using a Directive. Up vote this answer please, this needs to be on top! – Guillaume Filion Apr 22 '15 at 14:27
  • 3
    Much better solution than the accepted answer, for simple situation. – jcrowson Jun 07 '15 at 16:44
  • 1
    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! – Drumnbass May 14 '16 at 19:24
  • Brillance, this was a tremendous help and very neat to boot. – Artisan Mar 20 '17 at 08:41
57

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>
Stewie
  • 60,366
  • 20
  • 146
  • 113
  • 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? – asfallows Jan 08 '14 at 13:54
  • 3
    Ups, forgot to parse the value to int. Plunker fixed. Anyway, I'd call this an answer, not a tip. ;) – Stewie Jan 08 '14 at 14:20
  • 1
    THanks very much! Forgive my casual language, this is a good answer. :) – asfallows Jan 08 '14 at 15:17
  • is it possible to use the same for date comparison also? – Priya Apr 25 '14 at 12:59
  • Certainly is. Take my example, adjust it for date comparison and see how it goes. If problems then post on SO. – Stewie Apr 25 '14 at 17:59
  • 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 – Priya Apr 26 '14 at 08:59
  • Post new SO question and link it here. – Stewie Apr 26 '14 at 09:13
  • http://stackoverflow.com/questions/23308956/angularjs-directive-for-comparing-two-dates – Priya Apr 26 '14 at 09:23
  • 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? – greenoldman Jul 17 '16 at 19:45
  • 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...) – rekam Aug 24 '16 at 12:17
5

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()">
Nikos Paraskevopoulos
  • 39,514
  • 12
  • 85
  • 90
  • 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. – asfallows Jan 07 '14 at 22:12
  • 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... – Nikos Paraskevopoulos Jan 07 '14 at 22:16
2

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;
                    }
                };
            }
        };
    })
;
  • 1
    Thanks for posting! I used your method to create my own. If you're interested, I have it here... https://gist.github.com/deanofharvard/2db49ff36d1445eb6c63 – Fred Mar 24 '15 at 05:29
2

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;
            });
        }
    };
});
Peter Hedberg
  • 3,487
  • 2
  • 28
  • 36
0

You may take a look at https://github.com/nelsonomuto/angular-ui-form-validation

This provides a directive that is preconfigured with an api that exposes the scope and its models to your validator function.

Here is a plunker with the your specific use case: http://plnkr.co/edit/S0rBlS?p=preview

The syntax for the directives validators is as shown in the below example : { errorMessage: 'Cannot contain the number one', validator: function (errorMessageElement, val, attr, element, model, modelCtrl){ /** * The model and modelCtrl(scope) are exposed in the validator function * */ return /1/.test(val) !== true;
} }