5

I'm using ng-messages to validate my registration form fields, but I have a problem, I can't check if username is taken, until I send registration request to server. Currently I have this type of code:

UserPool.signUp(vm.form.username, vm.form.password, attributes)
    .then(function (result) {
        $state.go('app.pages_auth_confirm', {
            user: result.user
        });
    })
    .catch(function () {
        $scope.registerForm.username.$invalid = true;
        $scope.registerForm.username.$error.usernameAlreadyExists = true;
    });

and HTML:

<input name="username" ng-model="vm.form.username" placeholder="Name" required>
<div ng-messages="registerForm.username.$error" role="alert">
    <div ng-message="required">
        <span>Username field is required</span>
    </div>
    <div ng-message="usernameAlreadyExists">
        <span>User with this username already exists</span>
    </div>
</div>

and this works, but I want to know proper way of implementing that functionality. Registration form has many other fields also and changing some of them manually, for showing error, doesn't seems to be a good idea. I also did some research about validations and messages in angular and found out about custom validation and $asyncValidators but I can't use that functionality neither because as I understood I'll be needing some kind of API for getting information about used usernames only, but I don't have that kind of possibility, as I already said, I can't check if username is taken, until I send registration request. So can anybody tell me what should I do? What is the proper way for working with this kind of validation?

GROX13
  • 4,605
  • 4
  • 27
  • 41

3 Answers3

4

In order to achieve this in an angular way, create a Directive in the user-name input to check for the unique validation, and a factory to make an api call to get the username validation checked.

<input name="username" ng-model="vm.form.username" placeholder="Name" required name="username" unique> 

<div ng-messages="registerForm.username.$error" role="alert">
    <div ng-message="required">
        <span>Username field is required</span>
    </div>
    <div ng-message="unique">
        <span>User with this username already exists</span>
    </div>
</div>

the directive can be something like this,

In this directive we injected a factory called usernameservice, which returns the promise true or false.

myApp.directive('unique', function(usernameservice) {
  return {
    restrict: 'A',
    require: 'ngModel',
    link: function(scope, element, attrs, ngModel) {
      ngModel.$asyncValidators.unique = usernameservice;
    }
  };
});

Here is the factory, where your api call goes.

myApp.factory('usernameservice', function($q, $http) {
  return function(username) {
    var deferred = $q.defer();

    // your api vall goes her
    $http.get('/api/users/' + username).then(function() { // 
      // Found the user, therefore not unique.
      deferred.reject();
    }, function() {
      // User not found, therefore unique!
      deferred.resolve();
    });

    return deferred.promise;
  }
});

Now the message will be displayed every time the value is changed, let us delay the check so that, if user enter more letters, it gets called, let us use the ng-model-option debounce for it.

<input name="username" ng-model="vm.form.username" placeholder="Name" required name="username" unique ng-model-options="{ debounce: 100 }">  
Sravan
  • 18,467
  • 3
  • 30
  • 54
  • There is no way of knowing if username is valid unless I send sign up request with all the fields (username, password, email, phone and etc.) and as I understand that kind of solution will not work, only way of knowing if username is valid is after user pushes sign up button then I'll receive response which will contain status 400 with error message "Username already taken!" and that is only way for me to know that username is already used. – GROX13 Oct 03 '16 at 13:51
  • ok, but you dont want to add the async validation to the form in the controller as you did in example? – Sravan Oct 03 '16 at 13:54
  • do you get the error field name in response from the sign up, such as username is already taken, such sort of message? – Sravan Oct 03 '16 at 13:55
  • @Sravan excellent code, please don't remove. @ GROX13 that code answer your question. If what you need is only something like show/hide a text, use ng-show after your submit with return error. – Joao Polo Oct 03 '16 at 13:56
  • I would like to add async validation but it wouldn't work, because there is no way for me to check if username is valid unless I send signup request to server and that means if I send that request from async validator, if username isn't taken user will be registered without pushing signup button. – GROX13 Oct 03 '16 at 13:57
  • do one thing then, after changing the value for the username, make a request to registration api and when you get the response check in the response if you get username, error. if so, reject the request, else resolve it. – Sravan Oct 03 '16 at 14:00
  • I can't understand how would that solve my problem? In that case user will be registered anyway without pressing sign up button. You are telling me to send sign up request from validator which seems horrible idea to me. I agree that you wrote great code above, but that won't be useful in my case. – GROX13 Oct 03 '16 at 14:06
  • I need to add validation error somehow in catch block, but don't quite like putting it manually in `$error` array. – GROX13 Oct 03 '16 at 14:08
  • 1
    what my idea is, send only the username parameter to the sign up request, as the remaining fields will be anyways not filled, you will always get an error if the username is registered already, then you can submit at last with all the fields – Sravan Oct 03 '16 at 14:09
  • 1
    check the custom validation given in the official angular documentation, it also mentioned the same way of doing it. [form validation](https://docs.angularjs.org/guide/forms) – Sravan Oct 03 '16 at 14:17
  • Yea, that sounds good I'll have to do that if I won't be able to fix this different way, but anyway I'd like to avoid generating additional network traffic too. – GROX13 Oct 03 '16 at 14:23
3

This kind of code works for just adding validation error into the array:

$scope.registerForm.username.$setValidity("usernameAlreadyExists", false);

this should be called in catch block like this:

...
.catch(function () {
    $scope.registerForm.username.$setValidity("usernameAlreadyExists", false);
});

This code doesn't flags any of the form variables manually instead it uses interface method $setValidity which sets every necessary variable as needed.

GROX13
  • 4,605
  • 4
  • 27
  • 41
-1

In your html, put your form in

<ng-form name="myFormName">
    ... your form...
</ng-form>

This tag is the angular one form <form></form>. And you can put some <ng-form> inside <ng-form> etc. if needed.

Then, put a validator on your html component, such as "required" :

<ng-form name="myFormName">
    <input type="text" name="myinput" ng-required="true">
</ng-form>

Here, a class "invalid" will be put by angular on your component if it is invalid, i.e, not fill in our case.

You can then customize your component for "invalid" class.

If you want to check all your form error, tyou can display the $error variable. In our exemple, you can print myFormName.$error and check status with myFormName.$valid or myFormName.$invalid

M.Be
  • 312
  • 1
  • 4
  • 14
  • Humm... i think i misunderstand your last sentence when i answer you my first draft... So, you cannot alter HTML side at all ? – M.Be Oct 03 '16 at 12:14
  • I can change HTML that's not a point of that question. I don't have any kind of problems with html. I just don't like manually flagging anything. Not even `registerForm.username.$invalid` to `true`. I'll edit question and add HTML too. I think that is a good idea. – GROX13 Oct 03 '16 at 12:18