Remotely Validate Email Using AngularJS and NodeJS

There are many ways to remotely validate form fields depending on which JavaScript framework is being used. This tutorial talks about how to remotely validate email using AngularJS with the help of NodeJS.

Quick Scenario

Validating email remotely is one of the oldest ideas in development. One of the most obvious cases is validating email when a user signs up for an account for an app or a website. This is part of a good user experience because the sign up form gives a validation even before the user presses the submit button. Most websites today use emails as usernames and therefore client-side validation is important to prevent using the same email for different accounts.

Short Sidenote

This example can actually be used to remotely validate any field inside a <form> using AngularJS

Let’s start with the HTML form


<form name="signupForm" ng-submit="signMeUp(info)">

<div>
   <label>Email</label>
   <input type="email"
             name="email_addr"
             ng-model="info.email"/>
</div>

<button type="submit">Submit</button>

</form>

I’m naming this form as signupForm. It will be handy later on once we setup Angular validations.

Let’s add an error message to accompany the email field.


<div class="error-box">
   <span ng-show="signupForm.email_addr.$validEmail">
      This email address already exists.
   </span>
</div>

What’s going on here is that we only show this error message if the email address already exists. If the email doesn’t exist, and is therefore valid, then we don’t show any errors at all. Remember when I said that naming the form will be handy for validation? It’s used in the syntax formName.fieldName.$validationName. Just like up there, signupForm is the name followed by the email field’s name email_addr. It’s then followed by the validation string which we’ll create soon enough.

To remotely validate the email field, we just have to make an AngularJS directive. And then we will hook this directive to the email field. I will show the directive implementation in step by step.


.directive('validateEmailRemotely', function() {
   return {
    restrict: 'A',
        scope: true,
        require: 'ngModel',
        link: function(scope, elem, attrs, ctrls) {
          var ngModel = ctrls;
        }
  }
})

This directive is strictly an attribute: I just want it to be added to the email field and not be a new HTML element or anything else. So I’m restricting it to “A”. This directive will have its own scope because I don’t ever want this directive to access the form’s scope, in case it messes it up (during this process or in the future). I’m requiring ngModel of the email field so that I can set its validation. The entity of ngModel will be in the ctrls parameter of the link function. See ngModel validation for more info.

Next, we’re now going to work inside the link function.


.directive('validateEmailRemotely', function($http) {
   return {
     restrict: 'A',
        scope: true,
        require: 'ngModel',
        link: function(scope, elem, attrs, ctrls) {
            var ngModel = ctrls;
            scope.$watch(attrs.ngModel, function(email) {
              // Remote validation happens here
            });
        }
  }
});

In the above code, I injected the $http AngularJS service so that we can asynchronously check the email address that the user entered. To get the email field value, we watch the changes of the email field’s ng-model attribute. At the same time we get the value through the callback function.

Below is the code inside the callback function. This is where we start using Angular validations. That also means that this is where we setup that validity string validEmail so that HTML error message shows up.


scope.$watch(attrs.ngModel, function(email) {
   var url = 'remotehost.com/api/valid_email?email='+email;

   $http.get(url)
   .then(function(data) {
      if (data.email_valid) {
           ngModel.$setValidity('validEmail', false);
      } else {
           ngModel.$setValidity('validEmail', true);
      }
    }, function(error) {
         ngModel.$setValidity('validEmail', false);
    });
});

So every time the user types a character, the callback function is getting called and the email parameter gets the changes. For example if the user types “a”, the email variable gets “a”. When the user adds “b”, the email variable gets “ab”. And the value of the email variable is used as a value on the query string of the url. Then $http uses this url to remotely validate from the server-side. The $http service then calls the success callback function or the error callback function.

In the success callback, we expect the data from the server to be a boolean. So if data.email_valid is true then the email already exists and therefore we set the email field’s ngModel as invalid (false). If the email doesn’t exist, then the email field is valid (true). If for some reason an error occurred on the server, the error callback is called and we set the validity to false. Whatever the error is, we can’t validate the email field because the error might not tell us that the email already exists or not.

We’re not done yet. We need to hook this directive to the email field in the form. Just add the name of the directive as an attribute to the email field like so:


<label>Email</label>
<input type="email" 
          name="email_add"
          ng-model="info.email"
          validate-email-remotely /> <!--added here -->

When you run the code and type-in an existing email, the error message will show up. If you type an email that doesn’t exist on record, then the error message won’t show up.

There’s a flaw in the HTML (yeah, right now).

Even if the email is invalid and the error message is showing, the user can still hit the submit button. To prevent this, we need to use the ng-disabled directive on the submit button.


<button type="submit" 
       ng-disabled="signupForm.$invalid">Submit</button>

So as long as the email is invalid, the submit button is disabled. This is where naming the <form> helps too.

Clean up the directive.

So even when the directive is working as expected, we need to make a .service to handle the $http requests. This will create some organization. The directive will only set validation on the email field and a service will do the talking to the server.


.service('AuthenticateService', function($http) {
   return {
     Email: function(email) {
        var url = 'remotehost.com/api/valid_email?email='+email;
    
        return $http.get(url)
           .then(function(response) {
               return response.data;
           }, function(error) {
               return error.data;
       });
     }
  };
});

So whether the success or error callbacks are called, we return server data response to the directive.

Back to the directive, we replace $http with AuthenticateService.


.directive('validateEmailRemotely', function(AuthenticateService) { 
...

The inside of scope.$watch is now


scope.$watch(attrs.ngModel, function(email) {
  AuthenticateService.Email(email)
  .then(function(result) {
      if (result.email_exists) {
      ngModel.$setValidity('validEmail', false);
      } else {
      ngModel.$setValidity('validEmail', true);
      }
   });
 });

Basically everything is the same except that the directive is no longer directly making the HTTP request. Which is a good thing because it’s the “angular” way. I kinda hate saying that but it’s true. Once you use this and start adding more services and directives, you might as well follow the “angular” way or you’ll bury yourself with difficulties.

What’s on the server side?

The example is using NodeJS and Express 4.

Defining the endpoint


var express = require("express");
var app = express(); 

app.get("/api/valid_email", function(req, res) {
 
});

Since email is usually stored in a database table, this example uses MySQL. There are a few Node libraries to work with MySQL Database. The one I’d recommend is node-mysql but I won’t fully explain it here. I’m just going to use an arbitrary example to show what’s going on inside the express endpoint shown above.


var mysql = require("some-mysql-lib");

app.get("/api/valid_email", function(req, res) {
    mysql.connect('host_db');
       
       mysql.query("SELECT email FROM person_table WHERE email = ?", [req.query.email], 
       function(error, rows) {
         if (error) {
           res.send(error);
         }

         if (rows.length == 1) { // we’re only expecting one result
            res.send({email_exists: true});
         } else {
            res.send({email_exists: false});
         }
       });
});

So inside the endpoint we connect to a database. Then we make a SELECT query to see if there’s a match in the person_table. If the result has one row, then the email exists. Otherwise, it’s false. And as shown in the directive, if the email exists then the error message shows up because it’s invalid. If the email doesn’t exist, then the error message won’t show up.