-2

I think I am misunderstanding Subscriptions and Observables and I am not finding the Angular documentation particularly helpful.

I am creating a validationService meant to check if a username is available. The validationService is called on form submit and will display an error message if the username is unavailable. The problem is, i have to submit the form twice for it to work.

The validationService makes a http call to the backend that returns a boolean. I have a local variable 'availableUsername' that I want to set the result to, so that I can use it elsewhere. Inside the subscribe function it works fine, I get the result and I set it to this variable. But when I leave the scope of the subscribe method, the variable is undefined. But when I call submit again, it works.

I have added the validationService and userService below.

validationService.ts

import { Injectable } from '@angular/core';
import { FormControl } from '@angular/forms';

import { AlertService } from './../services/alert.service';
import { UserService } from './../services/user.service';

@Injectable({
  providedIn: 'root'
})
export class ValidationService {

  availableUsername: boolean;
  availableEmail: boolean;

  constructor(
    private alertService: AlertService,
    private userService: UserService
    ) { }

  validateUsername(controls: any): boolean {

    const username = controls.username.value;

    this.isUsernameAvailable(username);

    if (!this.availableUsername) {
      this.alertService.error('This username is already taken', false);
      return true;
    }
    return false;
  }

  private isUsernameAvailable(username: string) {

    if (username === undefined || username === '') {
      return;
    }

    this.userService.isUserNameAvailable(username)
      .subscribe((result) => {
        this.availableUsername = result;
    },
    error => {
      this.alertService.error(error);
      return;
    });
  }

}

userService.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

import { environment } from './../../environments/environment';

@Injectable({ providedIn: 'root' })
export class UserService {

  apiUrl: string = environment.apiUrl;

  constructor(private http: HttpClient) { }

  isUserNameAvailable(username: string) {
    return this.http.get<boolean>(`${this.apiUrl}/users/checkUsernameAvailability/${username}`);
  }

}
halfer
  • 19,824
  • 17
  • 99
  • 186
Jack Lovett
  • 1,061
  • 9
  • 12
  • 1
    The A in AJAX means **asynchronous**. The function passed to subscribe(), which initializes `this.availableUsername`, is executed loooooong after your validator has returned its result. You need to use an asyncValidator. But most importantly, you need to understand the principle of asynchrony. – JB Nizet Mar 23 '19 at 12:47
  • So validateUsername isn't waiting for isUsernameAvailable to finish? – Jack Lovett Mar 23 '19 at 13:23
  • Yes, it does. And as you can see, isUsername doesn't return anything. All it does is to send a http request and return immediately. At that time, isUsernameAvailable() has finished. The response to the request, however, will be handled by the callback, much later. Just like, if I tell you "please toast me a piece of bread", you'll have finished that task in 2 seconds: the time it takes to put the bread in the toaster. I will only be able to eat the toast much later though: when the toaster rings to notify me that the toast is ready. – JB Nizet Mar 23 '19 at 13:27
  • And while the toaster is slowly toasting the bread, you and I do plenty of other things. – JB Nizet Mar 23 '19 at 13:28

2 Answers2

0
this.isUsernameAvailable(username); 

when the above this line of code is excuted it will run asynchronously so before assigning a value to this.availableUsername = result; by the subscription the below code will run

 if (!this.availableUsername) {
      this.alertService.error('This username is already taken', false);
      return true;
    }
    return false;
  }

To avoid that what have to do is check this condition inside the subscription like this

     this.userService.isUserNameAvailable(username)
          .subscribe((result) => {
            this.availableUsername = result;
        },
        error => {
          this.alertService.error(error);
          return;
        }
         ()=>{
 if (!this.availableUsername) {
      this.alertService.error('This username is already taken', false);

    }
}
);
      }

    }
-1

The problem in your code is that you trying to use asynchronous flow synchronously the availableUsername property value will set only when the subscribe call back is called, which will be long after the if (!this.availableUsername).

in this case you could use asyncValidartor which built for those cases.

Another (less recommended) alternative is making your code run synchronously use async and await.

itay oded
  • 978
  • 13
  • 22