AngularJS Form Validation

Shows how AngularJS and CSS can play together for form validation. AngularJS port of this jQuery Password strength verification: http://www.webdesignerdepot.com/2012/01/password-strength-verification-with-jquery/

by adamdbradley

HTML

<link rel="stylesheet" href="http://twitter.github.com/bootstrap/assets/css/bootstrap.css">
<form ng-app="form-example" class="row form-horizontal" novalidate>
 <div class="control-group">
  <label class="control-label" for="inputEmail">Email</label>
  <div class="controls">
    <input type="email" id="inputEmail" placeholder="Email" ng-model="email" required>
    <div class="input-help">
      <h4>Invalid Email</h4>
    </div>
  </div>
 </div>
<div class="control-group">
  <label class="control-label" for="inputPassword">Password</label>
  <div class="controls">
    <input ng-model="password" class="immediate-help" password-validate required type="password" id="inputPassword" placeholder="Password">
    <div class="input-help">
      <h4>Password must meet the following requirements:</h4>
      <ul>
        <li ng-class="pwdHasLetter">At least <strong>one letter</strong></li>
        <li ng-class="pwdHasNumber">At least <strong>one number</strong></li>
        <li ng-class="pwdValidLength">At least <strong>8 characters long</strong></li>
      </ul>
    </div>
  </div>
 </div>
<div class="control-group">
  <div class="controls">
    <button type="submit" class="btn">Create Account</button>
    <button class="btn" disabled>Create Account</button>
  </div>
 </div>
</form>

CSS

.input-help {
  display: none;
  position:absolute;
  z-index: 100;
  top: -6px;
  left: 160px;
  width:200px;
  padding:10px;
  background:#fefefe;
  font-size:.875em;
  border-radius:5px;
  box-shadow:0 1px 3px #aaa;
  border:1px solid #ddd;
  opacity: 0.9;
}
.input-help::before {
  content: "\25C0";
  position: absolute;
  top:10px;
  left:-12px;
  font-size:16px;
  line-height:16px;
  color:#ddd;
  text-shadow:none;
}
.input-help h4 {
  margin:0;
  padding:0;
  font-weight: normal;
  font-size: 1.1em;
}

/* Always hide the input help when it's pristine */
input.ng-pristine + .input-help {
  display: none;
}

/* Hide the invalid box while the input has focus */
.ng-invalid:focus + .input-help {
  display: none;
}

/* Show a blue border while an input has focus, make sure it overrides everything else */
/* Overriding Twitter Bootstrap cuz I don't agree we need to alarm the user while they're typing */
input:focus {
  color: black !important;
  border-color: rgba(82, 168, 236, 0.8) !important;
  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6) !important;
  -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6) !important;
  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6) !important;
}


/* Show green border when stuff has been typed in, and its valid */
.ng-dirty.ng-valid {
  border-color:#3a7d34;
}

/* Show red border when stuff has been typed in, but its invalid */
.ng-dirty.ng-invalid {
  border-color:#ec3f41;
}

/* Show the help box once it has focus */
.immediate-help:focus + .input-help {
  display: block;
}

/* Immediate help should be red when pristine */
.immediate-help.ng-pristine:focus + .input-help {
  border-color:#ec3f41;
}
.immediate-help.ng-pristine:focus + .input-help::before {
  color:#ec3f41;
}

/* Help hould be green when input is valid */
.ng-valid + .input-help {
  border-color:#3a7d34;
}
.ng-valid + .input-help::before {
 ...

JavaScript

var app = angular.module('form-example', []);

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

                scope.pwdValidLength = (viewValue && viewValue.length >= 8 ? 'valid' : undefined);
                scope.pwdHasLetter = (viewValue && /[A-z]/.test(viewValue)) ? 'valid' : undefined;
                scope.pwdHasNumber = (viewValue && /\d/.test(viewValue)) ? 'valid' : undefined;

                if(scope.pwdValidLength && scope.pwdHasLetter && scope.pwdHasNumber) {
                    ctrl.$setValidity('pwd', true);
                    return viewValue;
                } else {
                    ctrl.$setValidity('pwd', false);                    
                    return undefined;
                }

            });
        }
    };
});