AngularJS: integrating with server-side validation
This is another case where a custom directive is your friend. You'll want to create a directive and inject $http or $resource into it to make a call back to the server while you're validating.
Some pseudo code for the custom directive:
app.directive('uniqueEmail', function($http) { var toId; return { restrict: 'A', require: 'ngModel', link: function(scope, elem, attr, ctrl) { //when the scope changes, check the email. scope.$watch(attr.ngModel, function(value) { // if there was a previous attempt, stop it. if(toId) clearTimeout(toId); // start a new attempt with a delay to keep it from // getting too "chatty". toId = setTimeout(function(){ // call to some API that returns { isValid: true } or { isValid: false } $http.get('/Is/My/EmailValid?email=' + value).success(function(data) { //set the validity of the field ctrl.$setValidity('uniqueEmail', data.isValid); }); }, 200); }) } }});
And here's how you'd use it in the mark up:
<input type="email" ng-model="userEmail" name="userEmail" required unique-email/><span ng-show="myFormName.userEmail.$error.uniqueEmail">Email is not unique.</span>
EDIT: a small explanation of what's happening above.
- When you update the value in the input, it updates the $scope.userEmail
- The directive has a $watch on $scope.userEmail it set up in it's linking function.
- When the $watch is triggered it makes a call to the server via $http ajax call, passing the email
- The server would check the email address and return a simple response like '{ isValid: true }
- that response is used to $setValidity of the control.
- There is a in the markup with ng-show set to only show when the uniqueEmail validity state is false.
... to the user that means:
- Type the email.
- slight pause.
- "Email is not unique" message displays "real time" if the email isn't unique.
EDIT2: This is also allow you to use form.$invalid to disable your submit button.
I needed this in a few projects so I created a directive. Finally took a moment to put it up on GitHub for anyone who wants a drop-in solution.
https://github.com/webadvanced/ng-remote-validate
Features:
Drop in solution for Ajax validation of any text or password input
Works with Angulars build in validation and cab be accessed at formName.inputName.$error.ngRemoteValidate
Throttles server requests (default 400ms) and can be set with
ng-remote-throttle="550"
Allows HTTP method definition (default POST) with
ng-remote-method="GET"
Example usage for a change password form that requires the user to enter their current password as well as the new password.:
<h3>Change password</h3><form name="changePasswordForm"> <label for="currentPassword">Current</label> <input type="password" name="currentPassword" placeholder="Current password" ng-model="password.current" ng-remote-validate="/customer/validpassword" required> <span ng-show="changePasswordForm.currentPassword.$error.required && changePasswordForm.confirmPassword.$dirty"> Required </span> <span ng-show="changePasswordForm.currentPassword.$error.ngRemoteValidate"> Incorrect current password. Please enter your current account password. </span> <label for="newPassword">New</label> <input type="password" name="newPassword" placeholder="New password" ng-model="password.new" required> <label for="confirmPassword">Confirm</label> <input ng-disabled="" type="password" name="confirmPassword" placeholder="Confirm password" ng-model="password.confirm" ng-match="password.new" required> <span ng-show="changePasswordForm.confirmPassword.$error.match"> New and confirm do not match </span> <div> <button type="submit" ng-disabled="changePasswordForm.$invalid" ng-click="changePassword(password.new, changePasswordForm);reset();"> Change password </button> </div></form>
I have created plunker with solution that works perfect for me. It uses custom directive but on entire form and not on single field.
http://plnkr.co/edit/HnF90JOYaz47r8zaH5JY
I wouldn't recommend disabling submit button for server validation.