AngularJS Password Match form validation

AngularJS has some pretty interesting augmentation on the way forms work. Some basic validation is supported out of the box.

Form validation is “activated” when input controls are inside a form tag.  And also, form control states are created which you can use to “check” or validate a particular control.

For example, by adding the markup shown below, (Bootstap 3 styled):

<form role="form" name="myForm" novalidate="" data-ng-submit="submitForm()">
<div class="form-group" data-ng-class="{'has-error':!myForm.userName.$valid}"><label for="userName">User Name</label>
 <input class="form-control" type="text" name="userName" placeholder="Enter something" required="" data-ng-model="UserName" />
 <span class="help-block" data-ng-show="myForm.userName.$error.required">This is required.</span></div>
</form>

you essentially will have a resulting UI that looks like what is shown below, when nothing is typed in the input field (since it is required)
image.png
What “enabled” the required validation? By simply putting the required attribute, angularJS created for you a set of control states which you can use to your advantage. In the example above, we used the form control state myForm.userName.$valid to dynamically trigger the Bootstrap class has-error when this input control is “not valid”.

When the specific myForm.userName.$error.required occurs, we are also showing (data-ng-show) a hint on why is the control in an invalid state.

 

Key things to take note here:

  • Form has a name attribute with a value of myForm
  • Styled using Bootstrap 3 – you can style it with your own css styles!
  • The input control has a name attribute with a value of userName
  • Then input control has a data-ng-model attribute with a value of UserName. In your controller, you access the value of the input control thru $scope.UserName
  • Then input control has a required standard HTML5 attribute

OK, so much for the introduction. So how do we implement a custom form validation that makes sure that the user entered similar password values on a typical signup form, where the user is required to enter the password twice?

image.png

In this hypothetical scenario, we strongly suggest that these two fields are required, doh!  And that they should match!

image.png

The first input control, should have a similar markup with our intro above, except that we specify it to be of type “password”

<div class="form-group" data-ng-class="{'has-error':!myForm.password.$valid}"><label for="password">Password</label>
 <input class="form-control" type="password" name="password" required="" data-ng-model="Password" />
 <span class="help-block" data-ng-show="myForm.password.$error.required">This is required.</span></div>

And now for the “confirm password” field, it is still required, but we also need to verify that its value matches the value of the preceding input control. Since angularJS doesn’t have a native “validator” to do this (or at least that I am aware of), we need to roll our own. Validators are implemented ideally as directives, and lets say we will call our directive as ngMatch. Our “confirm password” input mark-up would then look like:

<div class="form-group" data-ng-class="{'has-error':!myForm.passwordCompare.$valid}"><label for="passwordCompare">Confirm Password</label>
 <input class="form-control" type="password" name="passwordCompare" required="" data-ng-model="PasswordCompare" data-ng-match="Password" />
 <span class="help-block" data-ng-show="myForm.passwordCompare.$error.required">This is required.</span>
 <span class="help-block" data-ng-show="myForm.passwordCompare.$error.match">Passwords do not match.</span></div>

Key things to take note:

  • Our input control has a new attribute called data-ng-match (resolves to ngMatch directive) “pointing” to the preceding control’s ngModel (Password)value – the one we want to test whether it matches with this control’s value.
  • A new span tag which will show up (data-ng-show) when the 2 values do not match.

And here’s how I implemented the ngMatch directive:


(function () {
'use strict';
var directiveId = 'ngMatch';
app.directive(directiveId, ['$parse', function ($parse) {

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

function link(scope, elem, attrs, ctrl) {
// if ngModel is not defined, we don't need to do anything
if (!ctrl) return;
if (!attrs[directiveId]) return;

var firstPassword = $parse(attrs[directiveId]);

var validator = function (value) {
var temp = firstPassword(scope),
v = value === temp;
ctrl.$setValidity('match', v);
return value;
}

ctrl.$parsers.unshift(validator);
ctrl.$formatters.push(validator);
attrs.$observe(directiveId, function () {
validator(ctrl.$viewValue);
});

}
}]);
})();

The heart of this “validator” directive is of course the validator function.

  • firstPassword(scope) retrieves the value of the first input control
  • ctrl.$setValidity(‘match’, v) sets the value of the “match” form state control that contributes to the validity of the input control, also referenced by the data-ng-show attribute (to show or hide the hint)

You can also use this trick for some other form validations like making sure the second number/date is greater than the first one, that the entered date is lesser than today, etc…

Disclaimer: The opinions expressed herein are my own personal opinions and do not represent my employer’s view in any way.
Tagged with: , ,
Posted in angularJS
  • Alex Soroka

    Hi Eric,
    Thanks for the pretty nice idea about using $parsers, $formatters for that purposes. I have added some enhancement for your directive. I’m using $timeout. The $timeout service, in this case, cancels setting up $setValidity whilst user is typing text. Also I use scope.$watch(attrs[‘ngMatch’], …) instead attrs.$observe(‘ngMatch’, …) because we should update validator function whenever original password has been updated.

    var firstPassword = $parse(attrs[directiveId]),

    timeout;

    var validator = function (value) {

    var temp = firstPassword(scope),
    v = value === temp;

    ctrl.$setValidity(‘match’, true);

    if (timeout) $timeout.cancel(timeout);

    timeout = $timeout(function () {

    ctrl.$setValidity(‘match’, v);

    }, 500);

    return value;

    }

    ctrl.$parsers.unshift(validator);

    ctrl.$formatters.push(validator);

    scope.$watch(attrs[directiveId], function () {

    validator(ctrl.$viewValue);

    });

  • Rob

    Do you have the full source available somewhere? This doesn’t work as you describe it above.

  • mdionne

    Hi Eric,

    Thanks for putting this together. I ran into an issue with this when there are other validations on the firstPassword control, such as minimum length, etc. In that case, firstPassword(scope) returns undefined, rather than the value in the control. Any thoughts on how to modify this to support that?

  • gururaj

    Thanx guys

  • https://www.anykeypc.com Chris Fagerstrom

    “Similarly, do not prefix your own directives with ng or they might conflict with directives included in a future version of Angular.”

    https://docs.angularjs.org/guide/directive