JamesS
JamesS

Reputation: 2300

Angular 10: Code doesn't wait for subscribe to finish

I have a button that when I click it, it logs the user into the database via an API call however the code doesn't seem to wait for the response until the second click of the button.

HTML:

<button class="login-button" [disabled]='!loginForm.valid' (click)="login()">Login User</button>

Login.Component.ts:

login() {
    this.getLoginDetails();

    // After the first click of the button this is 'undefined' but after the second it returns the details
    var details = this.loginResponse;

    // Store details in session
    localStorage.setItem('token', details.token);   // Throws exception on first click due to 'details' being undefined
    localStorage.setItem('refreshToken', details.refreshToken);
    localStorage.setItem('userId', details.userId.toString());

    this.router.navigate(['/subscribers']);
}

getLoginDetails(){
    var data = JSON.stringify({
        "EmailAddress": this.loginForm.controls['userName'].value,
        "Password": this.loginForm.controls['password'].value 
    });

    const httpOptions = {
        headers: new HttpHeaders({
            'Content-Type':  'application/json'
        })
    };

    // After the first click this is undefined but after the second it returns a value
    let resp = this.http.post("https://localhost:8080/api/auth/login", data, httpOptions);

    resp.subscribe((response: LoginResponse) => {
        if(response) {
            this.loginResponse = response
        } 
    });
}

The issue seems to be that after the first click, the post doesn't returns undefined but after the second it returns a value.

I believe that the issue is that the program isn't waiting for the API to return a value but then I don't understand why after the second click it works.

With Angular 10, does anyone know how to make sure the post is waited?

Upvotes: 2

Views: 10576

Answers (5)

Harsh Tukadiya
Harsh Tukadiya

Reputation: 183

You can use async and await. async:- The word “async” before a function means one simple thing: a function always returns a promise. Other values are wrapped in a resolved promise automatically.

Your code:-

    async login() {
   await this.getLoginDetails();
}

async getLoginDetails(){
    


//other implementation

    let resp = await this.http.post("https://localhost:8080/api/auth/login", data, httpOptions);

    resp.subscribe((response: LoginResponse) => {
    if(response) {
        this.loginResponse = response
    } 
  });

Upvotes: 1

Koray Kural
Koray Kural

Reputation: 168

Problem is you are not waiting for the response before assigning values. Here is what happens.

You send a request. Start waiting for response. Assign undefined to details. Response comes and loginResponse becomes valid. You send a second request. Assign response of the first request to details.

Here is the solution:

login() {
  this.getLoginDetails().subscribe((details: LoginResponse) => {
    if(details) {
      localStorage.setItem('token', details.token); 
      localStorage.setItem('refreshToken', details.refreshToken);
      localStorage.setItem('userId', details.userId.toString());

      this.router.navigate(['/subscribers']);
    } 
  });
}

getLoginDetails(){
    var data = JSON.stringify({
        "EmailAddress": this.loginForm.controls['userName'].value,
        "Password": this.loginForm.controls['password'].value 
    });

    const httpOptions = {
        headers: new HttpHeaders({
            'Content-Type':  'application/json'
        })
    };

    return this.http.post("https://localhost:8080/api/auth/login", data, httpOptions);
}

Upvotes: 1

Barremian
Barremian

Reputation: 31105

That is the nature of asynchronous data. It is assigned asynchronously, so you cannot assume that it is already assigned a value when you try to use it. Instead you need to wait for it to be assigned. In other words, subscribe to the observable where the response is required.

Try the following

login() {
  this.getLoginDetails().subscribe(
    (details: LoginResponse) => {
      if(details) {
        // Store details in session
        localStorage.setItem('token', details.token);
        localStorage.setItem('refreshToken', details.refreshToken);
        localStorage.setItem('userId', details.userId.toString());
        this.router.navigate(['/subscribers']);
      }
    },
    error => {
      // always good practice to handle errors from HTTP observables
    }
  );
}

getLoginDetails(): Observable<any> {     // <-- return the observable here
  var data = JSON.stringify({
    "EmailAddress": this.loginForm.controls['userName'].value,
    "Password": this.loginForm.controls['password'].value 
  });

  const httpOptions = {
    headers: new HttpHeaders({
      'Content-Type':  'application/json'
    })
  };

  return this.http.post("https://localhost:8080/api/auth/login", data, httpOptions);
}

You could find more info about returning data from async call here.

Upvotes: 2

Alexis
Alexis

Reputation: 1774

On your login() method, you call the getLoginDetails() where you do your subscribe. A subscribe is an event listener so your variable this.loginResponse will be populated only when your resp will render a value.

So finally in your login() method you attribute to details something that is not yet populated.

So I suggest you to do like this

login() {
    this.getLoginDetails().subscribe((response: LoginResponse) => {
        if(response) {

            // Store details in session
            localStorage.setItem('token', response.token);
            localStorage.setItem('refreshToken', response.refreshToken);
            localStorage.setItem('userId', response.userId.toString());

            this.router.navigate(['/subscribers']);
        } 
    });    
}

getLoginDetails(){
    var data = JSON.stringify({
        "EmailAddress": this.loginForm.controls['userName'].value,
        "Password": this.loginForm.controls['password'].value 
    });

    const httpOptions = {
        headers: new HttpHeaders({
            'Content-Type':  'application/json'
        })
    };

    return this.http.post("https://localhost:8080/api/auth/login", data, httpOptions);
}

Upvotes: 1

Jai
Jai

Reputation: 74738

Instead, you should return the resp and subscribe it in the login() method:

login() {
  this.getLoginDetails().subscribe((response: LoginResponse) => {
      // assign the response here
      var details = response;

      // Store details in session
      localStorage.setItem('token', details.token);   // Throws exception on first click due to 'details' being undefined
      localStorage.setItem('refreshToken', details.refreshToken);
      localStorage.setItem('userId', details.userId.toString());

      this.router.navigate(['/subscribers']);
   });
}

getLoginDetails(){
  // other implementation

  return resp;
}

Upvotes: 3

Related Questions