Reputation: 2300
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
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
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
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
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
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