0

I have a form label containing an input:

<label data-live-email-check="http://www.example-service-uri.com/">
    <span class="embedded-label">Email</span>
    <input ng-model="formData.email"
           type="email"
           name="email"
           placeholder="Email"
           required/>
    <span class="message" ng-show="emailValidationMessage">{{emailValidationMessage}}</span>
</label>

I want to create a directive that takes the URL provided by the data-live-email-check attribute and sends the email to that URL, validating whether or not it already exists.

angular.module("App").directive("liveEmailCheck", [function () {
    return {
        restrict: "A",
        scope: {
            ngModel: "="
        },
        link: function (scope, element, attrs) {
            scope.$watch(
                function(){
                    return scope.ngModel
                },
                function(newVal){
                    console.log(newVal);
                }
            );
        }
    }
}]);

I just want to watch the model on the input, and fire a request when it updates. Since the directive is defined on the label element ngModel is not properly bound. What am I doing wrong? My watch expression is not logging anything because it never fires.

I know I could manually grab the input, but that just seems like I'd be breaking the "angular pattern" that I feel so "bound" by. The frustrating thing is that there seems to be so many ways to do everything in Angular that I can never tell if I'm approaching a problem correctly or not.

--Edit--

To provide the solution that I personally would take (out of ignorance of a "better" way), would be the following:

angular.module("App").directive("liveEmailCheck", [function () {
    return {
        restrict: "A",
        require: ["^form"],
        link: function (scope, element, attrs, ctrl) {
            var formCtrl = ctrl[0];
            scope.formEl = formCtrl[element.find("input").attr("name")];


            scope.$watch(function(){return scope.formEl.$valid},  
                function(newVal){
                    console.log(newVal);
                });
        }
    }
}]);

This WORKS, but I feel like it "breaks the angular rules".

4
  • Why are you using it on the label? Are the label and it's content encapsulated in another directive? Commented Sep 9, 2015 at 19:59
  • Yes, they are, but that's unimportant (one should be able to use it without that wrapping directive). The important thing is that the emailValidationMessage should be able to respond according to the directive scope. Its a misleading name because this isn't for validation, its to check to see if the email exists. Commented Sep 9, 2015 at 20:18
  • How are you validating if the email exist or not with the implementation provided in the edit block? Commented Sep 9, 2015 at 23:44
  • I hadn't gotten far enough yet; I will say that the actual existence check is irrelevant at this point because I hadn't gotten to where I would be able to fire off the check! Commented Sep 9, 2015 at 23:53

2 Answers 2

0

A custom validation is written like this:

'use strict';

angular.module('App')
  .directive('liveEmailCheck', function (){

    return {
      require: 'ngModel',
      link: function (scope, elem, attr, ngModel){
        ngModel.$validators.liveEmailCheck= function (value){
         //Your logic here or in a factory
        };
      }
    };
  });

and then on your HTML it goes like

<input type="email" live-email-check="liveEmailCheck>

Basically you add your own validation to the set of build-in validations of angular.

Sign up to request clarification or add additional context in comments.

4 Comments

But I need the directive to be on the outer label element. The other tags need the directive scope because things like validationMessage, amongst other future additions all need to be in the directive scope. Can you stick to the markup provided, and provide a solution that way? Also, the naming is confusign- the directive is not intended to "validate" the input, its intended to "validate" whether or not the email exists at a remote data source.
Which raises another question, do I need to make two directives to accomplish this? This is one of those cases where Angular grosses me out. I feel like you should be able to accomplish this with one piece of code.
Hmmm, ok I see what you mean. Yes I know the feeling with angular. Directives are a complete mess! How about using your ng-model in your input and evaluate it as an expression in your directive with the {{formData.email}} and then use my custom validator??? Just a hunch...
Haha yeah, seems like that's what one might have to do, right? Sigh. I swear one day I'm gonna get the hang of this. Either way, I'll update this once I figure out what to do. Thanks for your help.
0

What you need here is an ng-model asyncValidator. Here is a simple implementation of such a directive.

angular.module("App").directive('asyncEmailValidator', function ($http) {
  return {
    require: 'ngModel',
    link: function (scope, element, attrs, ngModel) {
      var emailValidationUrl = attrs.asyncEmailValidator;
      ngModel.$asyncValidators.emailValidator = function (modelValue, viewValue) {
        var value = modelValue || viewValue;
        // NOTE: don't forget to correctly add the value to the url
        return $http(emailValidationUrl + value).then(function (validationResponse) {
          // NOTE: return rejected promise if validation failed else true
        });

      };
    }
  };
});

How you can use it in your case:

<label>
    <span class="embedded-label">Email</span>
    <input ng-model="formData.email"
           async-email-validator="http://www.example-service-uri.com/"
           type="email"
           name="email"
           placeholder="Email"
           required/>
    <span class="message" ng-show="<FormNameHere>.email.$error.emailValidator">
        {{emailValidationMessage}}
    </span>
</label>

This will be the right solution because it is implemented with angular ng-model validation which considers the validity of the model too.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.