How use async service into angular httpClient interceptor
Asked Answered
F

8

39

Using Angular 4.3.1 and HttpClient, I need to modify the request and response by async service into the HttpInterceptor of httpClient,

Example for modifying the request:

export class UseAsyncServiceInterceptor implements HttpInterceptor {

  constructor( private asyncService: AsyncService) { }

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // input request of applyLogic, output is async elaboration on request
    this.asyncService.applyLogic(req).subscribe((modifiedReq) => {
        const newReq = req.clone(modifiedReq);
        return next.handle(newReq);
    });
    /* HERE, I have to return the Observable with next.handle but obviously 
    ** I have a problem because I have to return 
    ** newReq and here is not available. */
  }
}

Different problem for the response, but I need again to applyLogic in order to update the response. In this case, the angular guide suggests something like this:

return next.handle(req).do(event => {
    if (event instanceof HttpResponse) {
        // your async elaboration
    }
}

But the "do() operator—it adds a side effect to an Observable without affecting the values of the stream".

Solution: the solution about request is shown by bsorrentino (into accepted answer), the solution about response is the follow:

return next.handle(newReq).mergeMap((value: any) => {
  return new Observable((observer) => {
    if (value instanceof HttpResponse) {
      // do async logic
      this.asyncService.applyLogic(req).subscribe((modifiedRes) => {
        const newRes = req.clone(modifiedRes);
        observer.next(newRes);
      });
    }
  });
 });

Therefore, how modify request and response with async service into the httpClient interceptor?

Solution: taking advantage of rxjs

Frohman answered 27/7, 2017 at 8:30 Comment(0)
U
15

I think that there is a issue about the reactive flow. The method intercept expects to return an Observable and you have to flatten your async result with the Observable returned by next.handle

Try this

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
      return this.asyncService.applyLogic(req).mergeMap((modifiedReq)=> {
        const newReq = req.clone(modifiedReq);
        return next.handle(newReq);
    });
}

You could also use switchMap instead of mergeMap

Unpremeditated answered 31/7, 2017 at 11:15 Comment(2)
I'm using Angular 8, and methods flatMap() and switchMap() are not avalable on my observable (returned by Store.slecte()).Cubeb
in your case you have to replace flatMap with mergeMapUnpremeditated
G
70

If you need to invoke an async function within interceptor then the following approach can be followed using the rxjs from operator.

import { MyAuth} from './myauth'
import { from, lastValueFrom } from "rxjs";

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  constructor(private auth: MyAuth) {}

  intercept(req: HttpRequest<any>, next: HttpHandler) {
    // convert promise to observable using 'from' operator
    return from(this.handle(req, next))
  }

  async handle(req: HttpRequest<any>, next: HttpHandler) {
    // if your getAuthToken() function declared as "async getAuthToken() {}"
    const authToken = await this.auth.getAuthToken()

    // if your getAuthToken() function declared to return an observable then you can use
    // const authToken = await lastValueFrom(this.auth.getAuthToken())

    const authReq = req.clone({
      setHeaders: {
        Authorization: authToken
      }
    })

    return lastValueFrom(next.handle(req));
  }
}
Garnetgarnett answered 29/8, 2019 at 14:14 Comment(5)
It works perfect. Also, I have added pipe handle before .toPromise() and all still working awesome. Thanks you.Oddity
It's working with Angular 11 as well. Thanks.Humo
Its not working for me . Check #69665866Unopened
.toPromise() is deprecated so use return await lastValueFrom(next.handle(req));Aylmer
where and how is authToken variable assigned?Wellnigh
U
15

I think that there is a issue about the reactive flow. The method intercept expects to return an Observable and you have to flatten your async result with the Observable returned by next.handle

Try this

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
      return this.asyncService.applyLogic(req).mergeMap((modifiedReq)=> {
        const newReq = req.clone(modifiedReq);
        return next.handle(newReq);
    });
}

You could also use switchMap instead of mergeMap

Unpremeditated answered 31/7, 2017 at 11:15 Comment(2)
I'm using Angular 8, and methods flatMap() and switchMap() are not avalable on my observable (returned by Store.slecte()).Cubeb
in your case you have to replace flatMap with mergeMapUnpremeditated
J
7

I am using an async method in my interceptor like this:

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

    public constructor(private userService: UserService) {
    }

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        return from(this.handleAccess(req, next));
    }

    private async handleAccess(req: HttpRequest<any>, next: HttpHandler):
        Promise<HttpEvent<any>> {
        const user: User = await this.userService.getUser();
        const changedReq = req.clone({
            headers: new HttpHeaders({
                'Content-Type': 'application/json',
                'X-API-KEY': user.apiKey,
            })
        });
        return next.handle(changedReq).toPromise();
    }
}
Jonette answered 18/9, 2019 at 19:14 Comment(0)
V
6

Asynchronous operation in HttpInterceptor with Angular 6.0 and RxJS 6.0

auth.interceptor.ts

import { HttpInterceptor, HttpEvent, HttpHandler, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs/index';;
import { switchMap } from 'rxjs/internal/operators';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

  constructor(private auth: AuthService) {}

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

    return this.auth.client().pipe(switchMap(() => {
        return next.handle(request);
    }));

  }
}

auth.service.ts

import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';

@Injectable()
export class AuthService {

  constructor() {}

  client(): Observable<string> {
    return new Observable((observer) => {
      setTimeout(() => {
        observer.next('result');
      }, 5000);
    });
  }
}
Vicinity answered 1/6, 2018 at 15:10 Comment(2)
Please explain your lines of code so other users can understand its functionality. Thanks!Gavan
In a real time environment do not forget to call observer.error() and define the cleanup logic in the returned unsubscribe() function. There is a nice example of this at angular.io's Observable Guide.Misesteem
D
1

The answers above seem to be fine. I had same requirements but faced issues due to update in different dependencies and operators. Took me some time but I found one working solution to this specific issue.

If you are using Angular 7 and RxJs version 6+ with requirements for Async Interceptor request then you can use this code which works with latest version of NgRx store and related dependencies:

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

    let combRef = combineLatest(this.store.select(App.getAppName));

    return combRef.pipe( take(1), switchMap((result) => {

        // Perform any updates in the request here
        return next.handle(request).pipe(
            map((event: HttpEvent<any>) => {
                if (event instanceof HttpResponse) {
                    console.log('event--->>>', event);
                }
                return event;
            }),
            catchError((error: HttpErrorResponse) => {
                let data = {};
                data = {
                    reason: error && error.error.reason ? error.error.reason : '',
                    status: error.status
                };
                return throwError(error);
            }));
    }));
Dvinsk answered 14/3, 2019 at 13:2 Comment(0)
R
0

Here's my solution in Angular 15, its for where I needed to modify all responses. Posting as it took me longer than I'd like to admit in order to get this working.

I use Nswag to generate request / response types and Mediatr on the API side. I have a generic response class with success & proceed bools. Every api call responds with these. This setup allows me to have tonnes of control, and makes the handling of errors and stuff neater when using toastr.

import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, HttpResponse, HttpStatusCode } from "@angular/common/http";
import { Injectable } from "@angular/core";
import { Utilities } from "somewhere";
import { ToastrService } from "ngx-toastr";
import { from, lastValueFrom, map } from "rxjs";

@Injectable()
export class ResponseHandlerInterceptor implements HttpInterceptor {
  constructor(public toastrService: ToastrService) { }

  intercept(req: HttpRequest<unknown>, next: HttpHandler) {
    return from(this.handle(req, next));
  }

  async handle(request: HttpRequest<any>, next: HttpHandler) {

    debugger;
    // request logic here, no access to response yet. 
    if (request.url == '/assets/config.dev.json') {

      // await async call
      let config = await lastValueFrom(Utilities.someFunctionWhichReturnsObservable());

      // can modify request after async call here, like add auth or api token etc.

      const authReq = request.clone({
        setHeaders: {
          Authorization: config.API_Token
        }
      })
    }

    return await lastValueFrom(next.handle(request).pipe(map(async response => {

      //response logic here, have access to both request & response

      if (response instanceof HttpResponse<any> && response.body instanceof Blob && response.body.size > 0) {

        // await async call
        let responseBody = await lastValueFrom(Utilities.readFile(response.body)) as string;

        //can modify response or do whatever here, i trigger toast notifications & possibly override status code 

        if (request.url.includes('/api/') && response.body.type === "application/json") {
          let genericResponse = JSON.parse(responseBody) as IGenericResponse<any>;

          if (genericResponse.success == false) {
            if (genericResponse.proceed == false) {
              this.toastrService.error(genericResponse.errorMessage, null, { progressBar: true, timeOut: 29000 });

              let responseOverride = {
                ...response,
                status: HttpStatusCode.InternalServerError
              }

              return responseOverride as HttpEvent<any>;
            }
          }
        } else if (response.body.type === "text/plain") {
          console.log(responseBody);
        }

      }

      return response;
    })));
  }
}
Rastus answered 15/5, 2023 at 16:43 Comment(0)
R
-2

Ok i am updating my answer, You cannot update the request or response in an asynchronous service, you have to update the request synchronously like this

export class UseAsyncServiceInterceptor implements HttpInterceptor {

constructor( private asyncService: AsyncService) { }

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
  // make apply logic function synchronous
  this.someService.applyLogic(req).subscribe((modifiedReq) => {
    const newReq = req.clone(modifiedReq);
    // do not return it here because its a callback function 
    });
  return next.handle(newReq); // return it here
 }
}  
Rosenberger answered 27/7, 2017 at 8:45 Comment(2)
i need async service as specified into the question and not the synchronous function as you propose. Are you sure about this affirmation: You cannot update the request or response in an asynchronous service ?Frohman
I am pretty sure because your request will be posted to server when it is being updated by your async service, same will happen to response response will be returned before the async service changes itRosenberger
P
-5

If I get your question right than you can intercept your request using deffer

   

module.factory('myInterceptor', ['$q', 'someAsyncService', function($q, someAsyncService) {  
    var requestInterceptor = {
        request: function(config) {
            var deferred = $q.defer();
            someAsyncService.doAsyncOperation().then(function() {
                // Asynchronous operation succeeded, modify config accordingly
                ...
                deferred.resolve(config);
            }, function() {
                // Asynchronous operation failed, modify config accordingly
                ...
                deferred.resolve(config);
            });
            return deferred.promise;
        }
    };

    return requestInterceptor;
}]);
module.config(['$httpProvider', function($httpProvider) {  
    $httpProvider.interceptors.push('myInterceptor');
}]);
Prosciutto answered 27/7, 2017 at 11:32 Comment(7)
Deferring a request means you are not doing an async operation, it will stop the request perform the operation and then forward itRosenberger
hehehe.......... deffering a request means giving your responsibility to $q.defferProsciutto
you are talking about angularjs, the post is related to angular 4Frohman
github.com/NgSculptor/ng2HttpInterceptor/blob/master/src/app/… you can refer this document if it helps youProsciutto
@muhammad hasnain, i used the approach suggested by you into my old project (angular 1.5), but it seems that angular 4 with httpClient is not able to do the same capability. The purpose of this question is to understand if angular 4 with httpClient have this limitation.Frohman
@pasquale I don't know about angular 4 but just searched for your issue. Look into following thread if it solves your problem. https://mcmap.net/q/409271/-angular-4-http-interceptorProsciutto
unfortunately, extends http class is not a adoptable solution in my context, because I need httpClient and its flexible configuration on interceptorsFrohman

© 2022 - 2024 — McMap. All rights reserved.