When accepting user input via forms, there is a lot that can go wrong. Usually we must ensure that user entered input is valid before accepting that input. Instead of waiting until the submitted form data gets to the server, we want to use JavaScript to ensure the form is valid before the user can submit it.

Angular provides all of the pieces we need to perform form validation in the browser. One of those pieces are input validators. Input validators are simple JavaScript functions which accept a form value and return true if the input is valid, or false otherwise. Input validators are then wrapped in directives which can be applied to form tags (such as the <input> tag). Many powerful input validators are include with Angular. Please refer to Angular’s guide on forms for more information on how form validation works.

For a current project, I wanted to validate that a user’s password was equal to their confirmed password field; a very common use case for websites that perform authentication. It was easy to write an Angular custom form validator which will fulfilled our requirements.

Solution

equalDirective.js

I started off by declaring a directive named equal. The directive is responsible for registering the custom validator; meaning we will need to require the ngModel directive. By requiring the ngModel directive, we can add our custom validator to the $validators object as well as control when validation happens. Notice that the equality validator ended up being a simple JavaScript function:

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

Lastly, we register a $watch on the two way bound equal scope variable. This allows us to trigger a new ngModel validation sequence every time the value of equal changes:

scope.$watch('equal', function(newVal, oldVal) {
  ctrl.$validate();
});

Here is the finished directive:

angular
  .module('customValidator', [])
  .controller('FormController', function() {})

  .directive('equal', function() {

    return {
      require: 'ngModel',
      scope: {
        equal: '='
      },
      link: function(scope, elem, attrs, ctrl) {

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

        scope.$watch('equal', function(newVal, oldVal) {
          ctrl.$validate();
        });
      }
    };
  });

index.html

Once the directive is complete, it just needs to be added to the form. The directive should be added as an attribute to the form field you want to check for equality. Make sure to pass in the scope variable you want to compare against: equal="password".

Here is how I used the equal directive with a password and password confirmation field:

<div>
  <!-- Password field -->
  <input type="text" name="password"
    required
    id="password"
    ng-model="password"
    placeholder="Password" />
</div>
<div>
  <!-- Confirm Passwrod field -->
  <input type="text" name="confirmPassword"
    equal="password"
    required
    id="confirmPassword"
    ng-model="confirmPassword"
    placeholder="Confirm Password" />
</div>

Here are the full contents of index.html; showing how to check for validation errors with the equal directive:

<div ng-app="customValidator" ng-controller="FormController">
    <div ng-form="foo">
      <div>
        <!-- Password field -->
        <input type="text" name="password"
          required
          id="password"
          ng-model="password"
          placeholder="Password" />
      </div>
      <div>
        <!-- Confirm Passwrod field -->
        <input type="text" name="confirmPassword"
          equal="password"
          required
          id="confirmPassword"
          ng-model="confirmPassword"
          placeholder="Confirm Password" />
      </div>

      <div>
        password is valid: {{foo.password.$valid}}
      </div>
      <div>
        confirmed password valid: {{foo.confirmPassword.$valid}}
      </div>
      <div>
        confirmed password equals password: {{!foo.confirmPassword.$error.equal}}
      </div>
    </div>
  </div>

Demo

Here is the full working example, hosted on Plunker: