11

Is there a way to validate one or multiple email addresses in a single input field?

The way that I'm currently exploring is by creating a custom directive that will split the input into different emails if a comma is detected. This is what I have so far:

angular.module('myApp')
  .directive('multipleEmails', function () {
    return {
      require: 'ngModel',
      link: function(scope, element, attrs, ctrl) {
        ctrl.$parsers.unshift(function(viewValue) {

          var emails = viewValue.split(',');
          // loop that checks every email, returns undefined if one of them fails.
        });
      }
    };
  });

The issue that I have with this is that I have been unable to call the angular email validator manually.

Edit: plunkr

Edit 2: Turns out that I can use angular 1.3

alexdmejias
  • 1,399
  • 4
  • 19
  • 34
  • `ctrl.$parsers` is an array, help yourself! But that sounds definitively like a bad UI. – Blackhole Dec 01 '14 at 17:19
  • @Blackhole can you give me an example of how that would work? I wish the ui could change but I think that is out of the question :( – alexdmejias Dec 01 '14 at 17:24
  • you could split on spaces rather than commas, remove commas, then check validity and set the validity – SoluableNonagon Dec 01 '14 at 17:31
  • @Blackhole - In my experience, an input field with delimited email addresses is quite common in a UI (i.e. Outlook) and in server-side code. The addresses are either separated by commas or semi-colons. – Brett Dec 01 '14 at 17:42
  • @SoluableNonagon not sure how this can test the validity of emails – alexdmejias Dec 01 '14 at 17:55
  • @alexdmejias, can you put together a plunker and I'll work on it? Here is a blank template: http://plnkr.co/edit/ufjSYH7jhy7Gn64cFoHa?p=info – SoluableNonagon Dec 01 '14 at 17:59
  • @SoluableNonagon here you go! http://plnkr.co/edit/2Ff2ea0tdrYHCmxUZgKo?p=preview – alexdmejias Dec 01 '14 at 18:15

6 Answers6

15

http://plnkr.co/edit/YrtXOphxkczi6cwFjvUp?p=preview

.directive('multipleEmails', function () {
  return {
    require: 'ngModel',
    link: function(scope, element, attrs, ctrl) {
      ctrl.$parsers.unshift(function(viewValue) {

        var emails = viewValue.split(',');
        // loop that checks every email, returns undefined if one of them fails.
        var re = /\S+@\S+\.\S+/;

        // angular.foreach(emails, function() {
          var validityArr = emails.map(function(str){
              return re.test(str.trim());
          }); // sample return is [true, true, true, false, false, false]
          console.log(emails, validityArr); 
          var atLeastOneInvalid = false;
          angular.forEach(validityArr, function(value) {
            if(value === false)
              atLeastOneInvalid = true; 
          }); 
          if(!atLeastOneInvalid) { 
            // ^ all I need is to call the angular email checker here, I think.
            ctrl.$setValidity('multipleEmails', true);
            return viewValue;
          } else {
            ctrl.$setValidity('multipleEmails', false);
            return undefined;
          }
        // })
      });
    }
  };
});
SoluableNonagon
  • 11,541
  • 11
  • 53
  • 98
  • 2
    Thank you, this is great. If you also want to allow empty inputs, do this: `var validityArr = emails.map(function (str) { if (viewValue) { return regexPattern.test(str.trim()); } else if (!viewValue) { return true; } }); // sample return is [true, true, true, false, false, false]` – TetraDev Mar 09 '16 at 21:13
  • To support "display name " I instead use this regex test: `var re = /^(?:([\w\s]+)\s*<(\w+)([\-+.][\w]+)*@(\w[\-\w]*\.){1,5}([A-Za-z]){2,6}>|(\w+)([\-+.][\w]+)*@(\w[\-\w]*\.){1,5}([A-Za-z]){2,6})$/;` – blalond Nov 29 '18 at 17:04
4

Instead of relying on the angular validator, I just ended up using a different regex. This is what I ended up with, which is very similar to what is shown here :

angular.module('myApp')
  .directive('multipleEmails', function () {
    return {
      require: 'ngModel',
      link: function(scope, element, attrs, ctrl ) {
        var emailsRegex = /^[\W]*([\w+\-.%]+@[\w\-.]+\.[A-Za-z]{2,4}[\W]*,{1}[\W]*)*([\w+\-.%]+@[\w\-.]+\.[A-Za-z]{2,4})[\W]*$/;
        ctrl.$parsers.unshift(function(viewValue) {
          if (emailsRegex.test(viewValue)) {
            ctrl.$setValidity('multipleEmails', true);
            return viewValue;
          } else {
            ctrl.$setValidity('multipleEmails', false);
            return undefined;
          }
        });
      }
    };
  });
alexdmejias
  • 1,399
  • 4
  • 19
  • 34
2

You can use ngPattern

<input ng-model="mymodel.attribute" required ng-pattern="someRegex">

where someRegex is set to a comma separated email address pattern.

AngularJS input.js has EMAIL_REGEXP that you can reuse for reconstructing the pattern. This is same as input[email] validation.

or something along these lines

Angularjs dynamic ng-pattern validation

Community
  • 1
  • 1
bhantol
  • 9,368
  • 7
  • 44
  • 81
2

This is very simple.. Note your input type should be email while using this directive.

//Html

 <input name="emailTextbox" type="email" email-validator 
 ng-model="vm.emailAdd"/>   

//Directive

  (function() {
      'use strict';

      angular
      .module('myApp')
      .directive('emailValidator', emailValidator);

      emailValidator.$inject = ['_'];
      /* @ngInject */
      function emailValidator(_) {
        var EMAIL_REGEXP = /^[_a-z0-9]+(\.[_a-z0-9]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$/;

        var directive = {
          require: 'ngModel',
          restrict: 'A',
          link: linkFunc
        };

        return directive;

        function linkFunc(scope, elm, attrs, ctrl) {

          if (ctrl && ctrl.$validators.email) {
            ctrl.$validators.email = function(modelValue) {
              if (angular.isDefined(modelValue)) {
                var isValidEmails = ctrl.$isEmpty(modelValue) || modelValue.split(',').every(
                  function (email) {
                    return EMAIL_REGEXP.test(email.trim());
                  }
                );
              }             
              return isValidEmails;
            };
          }
        }
      }

    })();
Surjeet Bhadauriya
  • 6,755
  • 3
  • 34
  • 52
2

I know it's a late answer, but uses the Angular 1.x logic as much as possible and only adds the multiple support. Other answers all use their own validation regex.

<input type="email" data-ng-multiple-email data-ng-model="$ctrl.x">

angular.module("myApp")
    .directive("ngMultipleEmail", function () {
        return {
            restrict: "A",
            require: "?ngModel",
            link: function (scope, elm, attrs, ctrl) {
                if (ctrl && ctrl.$validators.email) {
                    let originalValidator = ctrl.$validators.email;
                    ctrl.$validators.email = function (modelValue,   viewValue) {
                        var value = modelValue || viewValue || "",
                            valueSplit = value.split(/[,;]+/);
                        valueSplit.forEach(function (email) {
                            var valid = originalValidator(email.trim());
                            if (!valid) {
                                return false;
                            }
                        });
                        return true;
                    };
                }
            }
        };
    });
Niels Steenbeek
  • 4,692
  • 2
  • 41
  • 50
  • 1
    `ctrl.$validators.email` function always returns `true`. Consider `every()` instead of `forEach()`? – Qi Fan Jun 12 '17 at 22:32
1

angular.module('app1', [])
    .controller('appCont', formData)
    .directive('emailValidator', emailValidator);

    function formData($scope) { $scope.emails = 'hcl@hcl.com, mail@rrk.com';} 
    
    function emailValidator() {
        var EMAIL_REGEXP = /^[_a-z0-9]+(\.[_a-z0-9]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$/;

        function multiEmailValidationInTextBox(scope, elm, attrs, ctrl) {
            ctrl.$parsers.unshift(function(modelValue) {
                var isEmailsValid = true
                ,   isLastEmailValid = false;;
                
                if (angular.isDefined(modelValue)) {
                    if (!ctrl.$isEmpty(modelValue)) {
                        var splitEmails = modelValue.split(',')
                        ,   emailLength = splitEmails.length;

                        if (emailLength < 1) {
                            isValidEmails = EMAIL_REGEXP.test(splitEmails[0].trim()); 
                        } else {
                            angular.forEach(splitEmails, function(item, index) {
                                if (!EMAIL_REGEXP.test(splitEmails[index].trim()) && index != emailLength-1) {
                                    isValidEmails = false;                    
                                }
                            });

                            var lastEmail = splitEmails[emailLength-1].trim();
                            if (ctrl.$isEmpty(lastEmail) || 
                                (!ctrl.$isEmpty(lastEmail) && EMAIL_REGEXP.test(lastEmail)) ) {
                                isLastEmailValid = true;
                            }

                        }
                    }
                }        

                if (isEmailsValid && isLastEmailValid) {
                    elm.removeClass('has-error');
                    elm.addClass('has-success');
                } else {
                    elm.addClass('has-error');
                    elm.removeClass('has-success');
                }

                return isEmailsValid;
            });
        }

        return {
            require: 'ngModel',
            restrict: 'A',
            link: multiEmailValidationInTextBox
        };
    }
.has-error { background-color: red; }
        .has-success { background-color: #afdcaf; }

        input[type="text"] {
            width: 500px;
            font-family: sans-serif, monospace;
            font-size: 25px;
            border: 1px solid grey;
            border-radius: 4px;
            height: 30px;
            padding: 3px;
        }
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="app1" ng-controller="appCont"> <div>

    <input name="emailTextbox" type="text" email-validator ng-model="emails"/></div>

</body>
prudvi raju
  • 505
  • 1
  • 5
  • 19