Use a promise in Angular HttpClient Interceptor
Asked Answered
F

3

29

Can I use promise within HttpInterceptor? For example:

export class AuthInterceptor implements HttpInterceptor{
this.someService.someFunction()
    .then((data)=>{
       //do something with data and then
       return next.handle(req);
    });
}

why I need this? because I need to get a token to add to request header before making the request to the server.

My interceptor:

@Injectable()
export class AuthInterceptor implements HttpInterceptor{

    constructor(private authService: AuthService){}

    intercept(req: HttpRequest<any>, next: HttpHandler) : Observable<HttpEvent<any>>{
        console.log('Intercepted!');
        // return next.handle(req);
        this.authService.getToken()
            .then((token)=>{
                console.log(token);
                const reqClone = req.clone({
                    headers: req.headers
                            .set('Authorization', 'Bearer ' + token)
                            .append('Content-Type', 'application/json')
                });
                console.log(reqClone);
                return next.handle(reqClone);
            })
            .catch((err)=>{
                console.log('error in interceptor' + err);
                return null;
            });
    }
}

Request:

this.http.post(this.baseURL + 'hero', data)
                    .subscribe(
                            (res: any) => {
                                console.log('Saved Successfully.');
                                console.log(res);
                            },
                            (err: any) => {
                                console.log('Save Error.' + err);
                            }
                        );

Problems I am facing:

->I get this error before the promise is resolved.

You provided 'undefined' where a stream was expected. You can provide an Observable, Promise, Array, or Iterable.

Promise resloves and I get my token but after the error.

Feed answered 31/8, 2017 at 10:10 Comment(1)
I just checked my xhr requests under the developer tools, there's no 'hero' request there.So, the interceptor is not continuing the request?Feed
U
62

UPDATE: using [email protected]

import { from, Observable } from 'rxjs';
import { switchMap } from 'rxjs/operators';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

    constructor(private authService: AuthService){}

    intercept(request: HttpRequest<any>, next: HttpHandler) : Observable<HttpEvent<any>>{
        return from(this.authService.getToken())
              .pipe(
                switchMap(token => {
                   const headers = request.headers
                            .set('Authorization', 'Bearer ' + token)
                            .append('Content-Type', 'application/json');
                   const requestClone = request.clone({
                     headers 
                    });
                  return next.handle(requestClone);
                })
               );
    }
}

ORIGINAL ANSWER

Yes, you could inject the required service into the constructor method of the interceptor, and in the implementation of intercept retrieve the value, create a new updated http request and handle it.

I'm not good with promises, so you could try the following:

import { fromPromise } from 'rxjs/observable/fromPromise';

@Injectable()
export class AuthInterceptor implements HttpInterceptor{

    constructor(private authService: AuthService){}

    intercept(req: HttpRequest<any>, next: HttpHandler) : Observable<HttpEvent<any>>{
        return fromPromise(this.authService.getToken())
              .switchMap(token => {
                   const headers = req.headers
                            .set('Authorization', 'Bearer ' + token)
                            .append('Content-Type', 'application/json');
                   const reqClone = req.clone({
                     headers 
                    });
                  return next.handle(reqClone);
             });
    }
}
Unciform answered 31/8, 2017 at 10:49 Comment(12)
thanks bro. I have updated the question. hope its clearer now.Feed
It's working. Thanks a lot. was it not working because getToken() returns a promise not observable? @UnciformFeed
The return type of intercept has to be an observable, I dont now how to work with promises, thats why I transformed the promise into observable and applied operations in it. I guess your implementation wasnt returning an observable. If my answer helped you, please select it as answer so that ur question gets marked as solved :)Unciform
Yeah, I changed my function to observable and it's now working without fromPromise.Feed
Perfect, stick to observables; they can do all what a promise do and more :)Unciform
@Unciform May I ask why are you using switchMap here? From my understanding, getToken is converted to an observable, but what is so special about getting an auth token that can cause it to send more than one observable value?Lightweight
@JeffreyWen you could replace it with a mergeMap. There is no need to exclusively use switchMap for the reason that you pointed out :)Unciform
@Unciform I'm still relatively new to reactive programming, but why do we need to use things like switchMap, mergeMap, concatMap, etc. for something as simple as getting an Auth Token (probably from local storage?). Is omitting the switchMap/mergeMap entirely a viable solution?Lightweight
@JeffreyWen they are required in order to map the token to an inner-observable (the result of next.handle). The idea is to basically map the "token stream" (with just 1 event) into a new one. Because of the method´s signature, there are no viable approaches without using any of the mentioned operators.Unciform
@Unciform Thank you for clariying that!Lightweight
Something to note: you will not see the headers added to your clone request in your interceptor due to lazy setting of the headers. They won't be materialized until the final handler runs. I chased my tail on this wondering why I couldn't see my auth header. I traced into the angular source and figured it out.Norge
I can't believe how many hours I've wasted debugging this. Thank you very much. I have an interceptor in an Ionic Angular app, that needed to resolve a promise and then return the observable. I was only getting the resolved value from the promise, but with your answer, I'm getting the correct observable stream, and I can use reportProgress on my requests, etc. Cheers 🍻Padre
P
9

for RxJS 6+ I've updated Jota.Toledo's Answer:

import { fromPromise } from 'rxjs/observable/fromPromise';

@Injectable()
export class AuthInterceptor implements HttpInterceptor{
constructor(private authService: AuthService){}
intercept(req: HttpRequest<any>, next: HttpHandler) : Observable<HttpEvent<any>>{
    return fromPromise(this.authService.getToken())
          .pipe(switchMap(token => {
               const headers = req.headers
                        .set('Authorization', 'Bearer ' + token)
                        .append('Content-Type', 'application/json');
               const reqClone = req.clone({
                 headers 
                });
              return next.handle(reqClone);
         }));
 }  
}
Pert answered 25/5, 2018 at 17:2 Comment(1)
Thank you so muck, worked well. I tried others solution but none of them worked with Angular 6+.Mensuration
N
4

My turn (Thanks to Jota.Toledo and Capripio) :

1) Switch "fromPromise" to "from" --> fromPromise does not exist on type Observable

2) Fix 'application/json' quote

import { Observable, from } from 'rxjs';
import { switchMap } from 'rxjs/operators';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

  constructor(private authService: AuthService){}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

      return from(this.authService.getToken()).pipe(
        switchMap(token => {
            const headers = req.headers
                .set('Authorization', 'Bearer ' + token)
                .append('Content-Type', 'application/json');
            const reqClone = req.clone({
                headers
            });
            return next.handle(reqClone);
        }));
   }
}

My version of RxJs: "6.2.2" Hope that helped !

Neeley answered 22/10, 2018 at 19:30 Comment(1)
Why do we use the switchMap after converting the promise to an observable?Haberman

© 2022 - 2024 — McMap. All rights reserved.