48

I am trying to do a form validation using AngularJS. I am especially interested in comparing two values. I want the user to confirm some data he entered before he continues. Lets say I have the code below:

<p>
    Email:<input type="email" name="email1" ng-model="emailReg">
    Repeat Email:<input type="email" name="email2" ng-model="emailReg2">
<p>

and then I can use validation with:

<span ng-show="registerForm.email1.$error.required">Required!</span>
<span ng-show="registerForm.email1.$error.email">Not valid email!</span>
<span ng-show="emailReg !== emailReg2">Emails have to match!</span>  <-- see this line

registerForm.$valid will react correctly as to the text in inputs except I do not know how to use comparison within this validation to force the emails to be the same before allowing the user to submit the form.

I would love to have a solution without custom directives, but if this can't be achieved without it I will deal with it. Here is an answer that addresses similar issue with a custom directive.

Any help appreciated, thank you

Community
  • 1
  • 1
trainoasis
  • 5,531
  • 10
  • 43
  • 74

17 Answers17

46

You should be able to use ng-pattern/regex for comparing 2 input values

Email:<input type="email" name="email1" ng-model="emailReg">
Repeat Email:<input type="email" name="email2" ng-model="emailReg2" ng-pattern="emailReg">

and validation with:

<span ng-show="registerForm.email2.$error.pattern">Repeat Email should have the same value with email!</span>
Henry Neo
  • 2,317
  • 1
  • 21
  • 27
  • It won't work in the following case: email: abc, email2: abcd – Manto Feb 14 '15 at 01:10
  • it should work but if you are putting 'abc' or 'abcd' as input, those are not valid email addresses which probably failed the validation ahead of going into the pattern validation. – Henry Neo Feb 15 '15 at 22:20
  • this is close, just put the `emailReg` inside parentheses, I will add an example below. – dmo Aug 28 '15 at 18:07
  • 2
    This is brilliant. Already implemented a custom directive which is what everyone seems to suggest, but I'm totally ditching it since this feels to me a lot more in tune with the 'Angular way' of doing things! – Boris Oct 02 '15 at 08:53
  • 3
    Of course this fails when u have regex symbols. Also I would expect "test@test.com" to match "test@testacom" so this is a pretty terrible approach. - /test@test.com/.test('test@testacom') – Sam Nov 11 '16 at 01:11
  • 1
    @Sam , I avoided that issue by "escaping" the input, so its regex symbols became interpreted as literal matches. https://stackoverflow.com/questions/494035/how-do-you-use-a-variable-in-a-regular-expression/494122#494122 – The Red Pea Oct 29 '17 at 16:30
  • These other questions/answers are related to `ng-pattern`; [how-to-validate-email-id-in-angularjs-using-ng-pattern](https://stackoverflow.com/questions/24490668/how-to-validate-email-id-in-angularjs-using-ng-pattern) and [angularjs-dynamic-ng-pattern-validation](https://stackoverflow.com/questions/18900308/angularjs-dynamic-ng-pattern-validation) – The Red Pea Nov 09 '17 at 09:16
37

One way to achieve this is with a custom directive. Here's an example using a custom directive (ng-match in this case):

<p>Email:<input type="email" name="email1" ng-model="emailReg">
Repeat Email:<input type="email" name="email2" ng-model="emailReg2" ng-match="emailReg"></p>

<span data-ng-show="myForm.emailReg2.$error.match">Emails have to match!</span>

NOTE: It's not generally recommended to use ng- as a prefix for a custom directive because it may conflict with an official AngularJS directive.

Update

It's also possible to get this functionality without using a custom directive:

HTML

<button ng-click="add()></button>
<span ng-show="IsMatch">Emails have to match!</span>

Controller

$scope.add = function() {
  if ($scope.emailReg != $scope.emailReg2) {
    $scope.IsMatch=true;
    return false;
  }
  $scope.IsMatch=false;
}
bsplosion
  • 1,378
  • 15
  • 32
Ramesh Rajendran
  • 32,579
  • 35
  • 130
  • 205
  • Thanks for your answer. So I do have to use directive for this to work… no other way around probably so far? – trainoasis Mar 04 '14 at 13:34
  • 1
    @Why directive for this? you can simply use my code. It's relay easy. otherwise you need manually write some code – Ramesh Rajendran Mar 04 '14 at 13:36
  • 1
    Thank you, this is good - and I can still copy the directive from the page you linked and it works as well, so I will see what works best later. – trainoasis Mar 04 '14 at 13:43
  • 17
    ng-match is not a validation directive that is provided by the AngularJS framework. Please do not name your own custom directives with ng-*. The valid list of existing validators is available at https://docs.angularjs.org/api/ng/directive/input – Ryan Kimber Jul 23 '14 at 15:56
  • Just to explain @RyanKimber 's comment a little further...'ng' is the official Angular namespace. By using the 'ng' namespace you may be at risk for naming conflicts should your directive's name ever be used by the Angular team. Chances are pretty good that you can find a name the Angular will never be interested in - it's just best practice not to. [Angular docs](https://docs.angularjs.org/guide/directive#creating-directives) – Joe Hawkins Jan 30 '15 at 01:11
  • ng-match is external module right? so I must include it to project? – John Nguyen Feb 06 '15 at 08:28
  • 1
    bad link to ng-match – drogon Aug 30 '18 at 21:08
29

trainosais - you are right, validation should be done on a directive level. It's clean, modular and allows for reusability of code. When you have basic validation like that in a controller you have write it over and over again for different forms. That's super anti-dry.

I had a similar problem recently and sorted it out with a simple directive, which plugs in to the parsers pipeline, therefore stays consistent with Angular architecture. Chaining validators makes it very easy to reuse and that should be considered the only solution in my view.

Without further ado, here's the simplified markup:

<form novalidate="novalidate">
    <label>email</label>
    <input type="text"
        ng-model="email"
        name="email" />
    <label>email repeated</label>
    <input ng-model="emailRepeated"
        same-as="email"
        name="emailRepeated" />
</form>

And the JS code:

angular.module('app', [])
    .directive('sameAs', function() {
        return {
            require: 'ngModel',
            link: function(scope, elem, attrs, ngModel) {
                ngModel.$parsers.unshift(validate);

                // Force-trigger the parsing pipeline.
                scope.$watch(attrs.sameAs, function() {
                    ngModel.$setViewValue(ngModel.$viewValue);
                });

                function validate(value) {
                    var isValid = scope.$eval(attrs.sameAs) == value;

                    ngModel.$setValidity('same-as', isValid);

                    return isValid ? value : undefined;
                }
            }
        };
    });

The directive hooks into the parsers pipeline in order to get notified of any changes to the view value and set validity based on comparison of the new view value and the value of the reference field. That bit is easy. The tricky bit is sniffing for changes on the reference field. For that the directive sets a watcher on the reference value and force-triggeres the parsing pipeline, in order to get all the validators run again.

If you want to play with it, here is my pen: http://codepen.io/jciolek/pen/kaKEn

I hope it helps, Jacek

Jacek Ciolek
  • 428
  • 4
  • 5
  • 1
    On Angular 1.5.0, `ngModel.$setViewValue(ngModel.$viewValue);` is not causing the `$parsers` to run. I had to do `validate(ngModel.$viewValue);` instead. – Zane Aug 22 '16 at 23:58
  • This worked for me, however, in order to output a customised error I had to change the validation to 'sameas' not 'same-as' so that i could then use 'myform.emailaddress2.$error.sameas' – Code Pharaoh Dec 11 '17 at 13:30
12

I recently wrote a custom directive which can be generic enough to do any validation. It take a validation function from the current scope

module.directive('customValidator', [function () {
        return {
            restrict: 'A',
            require: 'ngModel',
            scope: { validateFunction: '&' },
            link: function (scope, elm, attr, ngModelCtrl) {
                ngModelCtrl.$parsers.push(function (value) {
                    var result = scope.validateFunction({ 'value': value });
                    if (result || result === false) {
                        if (result.then) {
                            result.then(function (data) {           //For promise type result object
                                ngModelCtrl.$setValidity(attr.customValidator, data);
                            }, function (error) {
                                ngModelCtrl.$setValidity(attr.customValidator, false);
                            });
                        }
                        else {
                            ngModelCtrl.$setValidity(attr.customValidator, result);
                            return result ? value : undefined;      //For boolean result return based on boolean value
                        }
                    }
                    return value;
                });
            }
        };
    }]);

To use it you do

<input type="email" name="email2" ng-model="emailReg2" custom-validator='emailMatch' data-validate-function='checkEmailMatch(value)'>
<span ng-show="registerForm.email2.$error.emailMatch">Emails have to match!</span>

In you controller then you can implement the method, that should return true or false

$scope.checkEmailMatch=function(value) {
    return value===$scope.emailReg;
}

The advantage is that you do not have to write custom directive for each custom validation.

Chandermani
  • 42,177
  • 11
  • 82
  • 86
  • Sorry, this needs to be fixed :(. I released this as validation library, check this blog http://blog.technovert.com/2014/03/angularjs-form-validation-library-directives/ and the source here https://github.com/technovert/angular-validation – Chandermani Sep 24 '14 at 09:48
8

When upgrading angular to 1.3 and above I found an issue using Jacek Ciolek's great answer:

  • Add data to the reference field
  • Add the same data to the field with the directive on it (this field is now valid)
  • Go back to the reference field and change the data (directive field remains valid)

I tested rdukeshier's answer (updating var modelToMatch = element.attr('sameAs') to var modelToMatch = attrs.sameAs to retrieve the reference model correctly) but the same issue occurred.

To fix this (tested in angular 1.3 and 1.4) I adapted rdukeshier's code and added a watcher on the reference field to run all validations when the reference field is changed. The directive now looks like this:

angular.module('app', [])
  .directive('sameAs', function () {
    return {
      require: 'ngModel',
      link: function(scope, element, attrs, ctrl) {
        var modelToMatch = attrs.sameAs;      

        scope.$watch(attrs.sameAs, function() {
          ctrl.$validate();          
        })

        ctrl.$validators.match = function(modelValue, viewValue) {
          return viewValue === scope.$eval(modelToMatch);
        };
      }
   };
});

Updated codepen

Community
  • 1
  • 1
br3w5
  • 3,460
  • 2
  • 30
  • 40
5

No need a function or a directive. Just compare their $modelValue from the view:

ng-show="formName.email.$modelValue !== formName.confirmEmail.$modelValue"

More detailed example:

<span ng-show="(formName.email.$modelValue !== formName.confirmEmail.$modelValue) 
                && formName.confirmEmail.$touched
                && !formName.confirmEmail.$error.required">Email does not match.</span>

Please note that the ConfirmEmail is outside the ViewModel; it's property of the $scope. It doesn't need to be submitted.

5

use ng-pattern, so that ng-valid and ng-dirty can act correctly

Email:<input type="email" name="email1" ng-model="emailReg">
Repeat Email:<input type="email" name="email2" ng-model="emailReg2" ng-pattern="emailReg">

<span ng-show="registerForm.email2.$error.pattern">Emails have to match!</span>
Cheng Li
  • 59
  • 1
  • 2
2

@Henry-Neo's method was close, it just needed more strict Regex rules.

<form name="emailForm">
    Email: <input type="email" name="email1" ng-model="emailReg">
    Repeat Email: <input type="email" name="email2" ng-model="emailReg2" ng-pattern="(emailReg)">
</form>

By including the regex rule inside parentheses, it will match the entire string of emailReg to emailReg2 and will cause the form validation to fail because it doesn't match.

You can then drill into the elements to find out which part is failing.

 <p ng-show="emailForm.$valid">Form Valid</p>
 <p ng-show="emailForm.email1.$error">Email not valid</p>
 <p ng-show="emailForm.email1.$valid && emailForm.email1.$error.pattern">
     Emails Do Not Match
 </p>
dmo
  • 4,048
  • 3
  • 22
  • 25
1

This module works well for comparing two fields. Works great with Angular 1.3+. Simple to use https://www.npmjs.com/package/angular-password

It also allows saving the module as a generic. Just include them in packages list of your module.

ravi punjwani
  • 476
  • 4
  • 11
1

Here is my simple version of the custom validator directive:

angular.module('app')
  .directive('equalsTo', function () {
    return {
      require: 'ngModel',
      link:    function (scope, elm, attrs, ngModel) {
        scope.$watchGroup([attrs.equalsTo, () => ngModel.$modelValue], newVal => {
          ngModel.$setValidity('equalsTo', newVal[0] === newVal[1]);
        });
      }
    };
  })
Yura Fedoriv
  • 159
  • 1
  • 2
  • 6
  • This was the only solution on this page that worked for me on Angular 1.6.4. Thanks! – Brad Dec 14 '17 at 17:28
0

Here is an angular 1.3 version of the sameAs directive:

angular.module('app').directive('sameAs', [function() {
  'use strict';

  return {
    require: 'ngModel',
    restrict: 'A',
    link: function(scope, element, attrs, ctrl) {
      var modelToMatch = element.attr('sameAs');      
      ctrl.$validators.match = function(modelValue, viewValue) {
        return viewValue === scope.$eval(modelToMatch);
      };
    }
  };
}]);
rdukeshier
  • 99
  • 4
0

Mine is similar to your solution but I got it to work. Only difference is my model. I have the following models in my html input:

ng-model="new.Participant.email"
ng-model="new.Participant.confirmEmail"

and in my controller, I have this in $scope:

 $scope.new = {
        Participant: {}
    };

and this validation line worked:

<label class="help-block" ng-show="new.Participant.email !== new.Participant.confirmEmail">Emails must match! </label>
0

Thanks for the great example @Jacek Ciolek. For angular 1.3.x this solution breaks when updates are made to the reference input value. Building on this example for angular 1.3.x, this solution works just as well with Angular 1.3.x. It binds and watches for changes to the reference value.

angular.module('app', []).directive('sameAs', function() {
  return {
    restrict: 'A',
    require: 'ngModel',
    scope: {
      sameAs: '='
    },
    link: function(scope, elm, attr, ngModel) {
      if (!ngModel) return;

      attr.$observe('ngModel', function(value) {
        // observes changes to this ngModel
        ngModel.$validate();
      });

      scope.$watch('sameAs', function(sameAs) {
        // watches for changes from sameAs binding
        ngModel.$validate();
      });

      ngModel.$validators.sameAs = function(value) {
        return scope.sameAs == value;
      };
    }
  };
});

Here is my pen: http://codepen.io/kvangrae/pen/BjxMWR

Community
  • 1
  • 1
0

You have to look at the bigger problem. How to write the directives that solve one problem. You should try directive use-form-error. Would it help to solve this problem, and many others.

    <form name="ExampleForm">
  <label>Password</label>
  <input ng-model="password" required />
  <br>
   <label>Confirm password</label>
  <input ng-model="confirmPassword" required />
  <div use-form-error="isSame" use-error-expression="password && confirmPassword && password!=confirmPassword" ng-show="ExampleForm.$error.isSame">Passwords Do Not Match!</div>
</form>

Live example jsfiddle

Stepan Kasyanenko
  • 3,106
  • 1
  • 11
  • 21
0

I need to do this just in one form in my entire application and i see a directive like a super complicate for my case, so i use the ng-patter like some has point, but have some problems, when the string has special characters like .[\ this broke, so i create a function for scape special characters.

$scope.escapeRegExp(str) {
  return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}

and in the view

<form name="ExampleForm">
  <label>Password</label>
  <input ng-model="password" required />
  <br>
   <label>Confirm password</label>
  <input ng-model="confirmPassword" required ng-pattern="escapeRegExp(password)"/>  
</form>
stalin
  • 3,332
  • 2
  • 22
  • 25
0

Of course for very simple comparisons you can always use ngMin/ngMax.

Otherwise, you can go with a custom directive, and there is no need of doing any $watch or $observe or $eval or this fancy $setValidity back and forth. Also, there is no need to hook into the postLink function at all. Try to stay out of the DOM as much as possible, as it is against the angular spirit.

Just use the lifecycle hooks the framework gives you. Add a validator and $validate at each change. Simple as that.

app.directive('sameAs', function() {
  return {
    restrict: 'A',
    require: {
      ngModelCtrl: 'ngModel'
    },
    scope: {
      reference: '<sameAs'
    },
    bindToController: true,
    controller: function($scope) {
      var $ctrl = $scope.$ctrl;

      //add the validator to the ngModelController
      $ctrl.$onInit = function() {
        function sameAsReference (modelValue, viewValue) {
          if (!$ctrl.reference || !modelValue) { //nothing to compare
            return true;
          }
          return modelValue === $ctrl.reference;
        }
        $ctrl.ngModelCtrl.$validators.sameas = sameAsReference;
      };

      //do the check at each change
      $ctrl.$onChanges = function(changesObj) {
        $ctrl.ngModelCtrl.$validate();
      };
    },
    controllerAs: '$ctrl'
  };
});

Your plunker.

Daniele Repici
  • 310
  • 2
  • 12
0

I've modified method of Chandermani to be compatible with Angularjs 1.3 and upper. Migrated from $parsers to $asyncValidators.

module.directive('customValidator', [function () {
    return {
        restrict: 'A',
        require: 'ngModel',
        scope: { validateFunction: '&' },
        link: function (scope, elm, attr, ngModelCtrl) {
            ngModelCtrl.$asyncValidators[attr.customValidator] = function (modelValue, viewValue) {
                return new Promise(function (resolve, reject) {
                    var result = scope.validateFunction({ 'value': viewValue });
                    if (result || result === false) {
                        if (result.then) {
                            result.then(function (data) {           //For promise type result object
                                if (data)
                                    resolve();
                                else
                                    reject();
                            }, function (error) {
                                reject();
                            });
                        }
                        else {
                            if (result)
                                resolve();
                            else
                                reject();
                            return;
                        }
                    }
                    reject();
                });
            }

        }
    };
}]);

Usage is the same

Proggear
  • 518
  • 4
  • 8