Angular Sign Up Form

Angular Sign Up Form

by Andrew Pougher

HTML

<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.2.1/angular.js"></script>
        <div class="container" ng-app="myApp">
            <div class="col-lg-offset-4 col-lg-4">
                <div class="panel">
                    <div class="panel-heading">Sign up</div>
                    <form name="form" method="post" action="/signup" novalidate>
                        <div class="form-group">
                            <label for="username" class="control-label">Username</label>
                            <input type="text" name="username" id="username" placeholder="Username" unique-username required ng-model="username" autofocus class="form-control"><i ng-show="busy" class="halflings-icon refresh rotating"></i><span ng-show="form.username.$dirty &amp;&amp; form.username.$error.required" class="help-block">Please choose a username</span><span ng-show="form.username.$dirty &amp;&amp; form.username.$error.isTaken" class="help-block">Username already taken</span><span ng-show="form.username.$dirty &amp;&amp; form.username.$error.invalidChars" class="help-block">Username may not contain any non-url-safe characters</span>
                        </div>
                        <div class="form-group">
                            <label for="email" class="control-label">Email</label>
                            <input type="email" ng-model="email" name="email" id="email" placeholder="Email" required class="form-control"><span ng-show="form.email.$dirty &amp;&amp; form.email.$error.email" class="help-block">Email is invalid</span>
                        </div>
                        <div class="form-group">
                            <label for="password" class="control-label">Password</label>
                            <input type="password" name="password" id="password" ng-model="password" required match="verification" class="form-control"><span ng-show="form.password.$dirty &amp;&amp; form.password.$error.required" class="help-block">Please enter a...

CSS

input.ng-invalid.ng-dirty {
  border-color: #b94a48;
  -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
  box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
}

input.ng-invalid.ng-dirty ~ .help-block {
  color: #b94a48;
}

input.ng-valid.ng-dirty {
  border-color: #468847;
  -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
  box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
}

@-webkit-keyframes rotating {
  from {
    -webkit-transform: rotate(0deg);
  }
  to {
    -webkit-transform: rotate(360deg);
  }
}

.rotating {
  float: right;
  position: relative;
  top: -24px;
  right: 10px;
  -webkit-animation: rotating 1s linear infinite;
}

JavaScript

var app = angular.module('myApp', []);

// 
app.directive('uniqueUsername', ['$http', function($http) {  
  return {
    require: 'ngModel',
    link: function(scope, elem, attrs, ctrl) {
      scope.busy = false;
      scope.$watch(attrs.ngModel, function(value) {
        
        // hide old error messages
        ctrl.$setValidity('isTaken', true);
        ctrl.$setValidity('invalidChars', true);
        
        if (!value) {
          // don't send undefined to the server during dirty check
          // empty username is caught by required directive
          return;
        }
        
        scope.busy = true;
        $http.post('/signup/check/username', {username: value})
          .success(function(data) {
            // everything is fine -> do nothing
            scope.busy = false;
          })
          .error(function(data) {
            
            // display new error message
            if (data.isTaken) {
              ctrl.$setValidity('isTaken', false);
            } else if (data.invalidChars) {
              ctrl.$setValidity('invalidChars', false);
            }

            scope.busy = false;
          });
      })
    }
  }
}]);

// http://codepen.io/brunoscopelliti/pen/ECyka

app.directive('match', [function () {
  return {
    require: 'ngModel',
    link: function (scope, elem, attrs, ctrl) {
      
      scope.$watch('[' + attrs.ngModel + ', ' + attrs.match + ']', function(value){
        ctrl.$setValidity('match', value[0] === value[1] );
      }, true);

    }
  }
}]);