43

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

8 Answers 8

74

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));
  }
}
Sign up to request clarification or add additional context in comments.

4 Comments

It works perfect. Also, I have added pipe handle before .toPromise() and all still working awesome. Thanks you.
Its not working for me . Check stackoverflow.com/questions/69665366/…
.toPromise() is deprecated so use return await lastValueFrom(next.handle(req));
where and how is authToken variable assigned?
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

2 Comments

I'm using Angular 8, and methods flatMap() and switchMap() are not avalable on my observable (returned by Store.slecte()).
in your case you have to replace flatMap with mergeMap
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();
    }
}

Comments

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);
    });
  }
}

2 Comments

Please explain your lines of code so other users can understand its functionality. Thanks!
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.
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);
            }));
    }));

Comments

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;
    })));
  }
}

Comments

-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
 }
}  

2 Comments

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 ?
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 it
-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');
}]);

7 Comments

Deferring a request means you are not doing an async operation, it will stop the request perform the operation and then forward it
hehehe.......... deffering a request means giving your responsibility to $q.deffer
you are talking about angularjs, the post is related to angular 4
github.com/NgSculptor/ng2HttpInterceptor/blob/master/src/app/… you can refer this document if it helps you
@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.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.