How to wait for a http request to finish in Angular?
Asked Answered
C

4

32

I've been building a SPA with Angular 4 on the front end and ASP.NET Core 2 on the Backend. Basically, I'm trying to automatically renew an user login, given a valid token. When I reload a page, for example, I would like that Angular stayed logged in. I'm storing the token on local storage, as well as an user object so I can check some user properties (if he/she is logged in for example).

How should I force angular to wait for the http request to finish? Here's the .net get Service:

get<T>(url: string): Observable<T> {
    let options = this.doHeaders();
    return this.doSub(this.http.get(this.doUrl(url), options));
}

private doSub<T>(obs: Observable<Response>) {
    var ob1: Observable<T> = obs.map(this.extractData)
        .catch(this.handleError);

    return ob1;
}

Here's the HTTP Request code:

   getUser() {

    if (this.storage.token == null || this.storage.token == '') {
        return;
    }
    
    this.net.get<LoginModel>(`Authentication/GetUser`).subscribe(t => {
        this.storage.token = t.token;
        this.storage.user = t.user;
        console.log(this.storage.user);
    });
}

Here's how I'm checking for the user:

export class StorageService {

    constructor(
        private storage: LocalStorageService
    ) {
    }

    public user: User = null;

    public get isLoggedIn(): boolean {
        return this.user != null;
    }

}

export class IsLoggedIn implements CanActivate {

constructor(
    private storage: StorageService,
    private router: Router
) { }

canActivate(
    childRoute: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
) {
    if (this.storage.isLoggedIn) {
        return true;
    }

    this.router.navigate(['account/login'], {
        queryParams: {
            return: state.url,
            message: 'You must be logged in'
        }
    });
    return false;
}}

The real issue is: Whenever I try to reload the page on a protected page, Angular does not wait for the http to complete, and redirects me to the login page. After about a few seconds, it logs in, but i've been redirected already. The above code is being executed on AppComponent (NgOnInit).

Capitalist answered 8/2, 2018 at 23:30 Comment(7)
Normally you don't ever wait for async operations to complete in Angular. That would block the thread. You could, for example return Observable<UserModel> if you cannot get the user, then you redirect (in the subscribe)Shivers
I’m not really sure if i understood correctly. Do you mean I should take out the subscribe from the AppComponent and subscribe it wherever im checking ?Capitalist
Something like that. Point is the whole app runs asynchronously using observables. I see your get user has no security, in example it's not asking for credentials. I imagine some kind of login page. Some you would do: if token is empty, go to login, subscribe to result of log in, if good continue, otherwise show login errorShivers
The getUser method will not return synchronously, you get it?Shivers
The GetUser gets the user from token (JWT). If the token is null i could redirect to login, but not all my pages are protected, so i only redirect if the user trying to access a protected route. I know Observables are async. I wouldnt care if there was any way to make a single synchronou http request. All i wanted was the application to wait for that request.Capitalist
If you want to secure routes take a look at route guards. That would be perfect. angular-2-training-book.rangle.io/handout/routing/…. "just" waiting it's a bad idea, again angular apps are asynchronous by nature to avoid blocking the single Javascript thread. If you still want to try and wait, you could try observable. To Promise. I think observables are easier to wait. Hope it helpsShivers
I am using Angular route guards. The issue is that i'm trying to get the user from local storage, and route guards dont wait for the user to populate and think i'm not logged in.Capitalist
L
29

You can use promises with async/await

async getUser() {
    if (this.storage.token == null || this.storage.token == '') {
        return;
    }

    const t = await this.net.get<LoginModel>(`Authentication/GetUser`).toPromise();

    this.storage.token = t.token;
    this.storage.user = t.user;

    console.log(this.storage.user);
}

See more:

Lambent answered 9/2, 2018 at 0:48 Comment(6)
Although this compiles and gets the user, it still doesn't solve my issue. I'm updating with a little more information, maybe i'm missing something somewhere else. Could you please have a look?Capitalist
Your call return this.doSub(this.http.get(this.doUrl(url), options)); should also be changed to await the get request, maybe you need to refactor code in other places as well?Lambent
I dont know much about async, sorry for being so amateur lol. Hmm i’ll try and change the doSub. This is the only request i need to wait so maybe i’ll create a new one just for itCapitalist
Please, read about httpInterceptor angular.io/guide/http#intercepting-requests-and-responses, read about switchMap and forget toPromiseCindycine
hi @Eliseo, could you explain the benefit of using switchmap over toPromise, as mentioned by you.Denigrate
@RohanAgarwal, In my opinion, concatenate Promise it's a little nightmare. Futhermore, Angular is made thinking in Observables, "await" to the response is make a sync calls, you wait until the promise resolve. For me is more natural make a call and control the response when you get the dataCindycine
P
7

Use .toPromise on your observable followed by async/await.

await this.net.get<LoginModel>(`Authentication/GetUser`).toPromise();
Philibeg answered 9/2, 2018 at 0:8 Comment(5)
Thanks for your reply, however the compiler doesnt let me do .toPromise() after subscribe or before. Maybe it's related to my net and get. I've updated the question.Capitalist
import 'rxjs/add/operator/toPromise'; 🌹Philibeg
Doesnt work before/after .subscribe. I have to remove subscribe in order for the compiler to accept. this.net.get<LoginModel>('Authentication/GetUser').toPromise(); Maybe did you mean: this.net.get<LoginModel>('Authentication/GetUser').toPromise().then(...);Capitalist
You don't need to call subscribe, you can get t from the return value of await ...Lambent
you cannot subscribe, you can get data add "await" flag before your method.Psych
S
4

Since .toPromise has been deprecated deprecations, you should use one of the two built in static conversion functions firstValueFrom or lastValueFrom.

It should be something like that

await firstValueFrom(this.net.get<LoginModel>(`Authentication/GetUser`));
Sheerlegs answered 24/10, 2022 at 7:14 Comment(0)
A
-3

You can not use the async/await syntax with Observables returned by Angular HttpClient so you first need to convert the observable to a promise first.

Luckily the Observable interface exports a toPromise() method which returns a promise from the observable:

await this.net.get<LoginModel>('Authentication/GetUser').toPromise();

See this example of how to use Async/Await with Angular HttpClient

Adellaadelle answered 6/2, 2020 at 16:51 Comment(1)
You added a duplicate answer.Chamois

© 2022 - 2024 — McMap. All rights reserved.