33

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

Pasquale Vitale
  • 569
  • 1
  • 6
  • 15

8 Answers8

63

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() {}"
    await this.auth.getAuthToken()

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

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

    return await lastValueFrom(next.handle(req));
  }
}
LuscaDev
  • 329
  • 3
  • 8
yottabrain
  • 2,387
  • 5
  • 23
  • 37
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

bsorrentino
  • 1,413
  • 11
  • 19
  • I'm using Angular 8, and methods `flatMap()` and `switchMap()` are not avalable on my observable (returned by `Store.slecte()`). – Ahmad Shahwan Jan 13 '20 at 14:28
  • in your case you have to replace `flatMap` with [`mergeMap`](https://www.learnrxjs.io/operators/transformation/mergemap.html) – bsorrentino Jan 16 '20 at 12:32
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();
    }
}
Kent Munthe Caspersen
  • 5,918
  • 1
  • 35
  • 34
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);
    });
  }
}
Volodymyr Kr
  • 61
  • 1
  • 2
  • 3
    Please explain your lines of code so other users can understand its functionality. Thanks! – Ignacio Ara Jun 01 '18 at 15:33
  • 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](https://angular.io/guide/observables#basic-usage-and-terms). – webpreneur Oct 20 '21 at 04:52
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);
            }));
    }));
Tofiq Quadri
  • 379
  • 5
  • 16
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;
    })));
  }
}
-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
 }
}  
Ghulam Mohayudin
  • 1,093
  • 10
  • 18
  • 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_ ? – Pasquale Vitale Jul 27 '17 at 10:50
  • 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 – Ghulam Mohayudin Jul 27 '17 at 11:03
-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');
}]);
muhammad hasnain
  • 501
  • 4
  • 16
  • Deferring a request means you are not doing an async operation, it will stop the request perform the operation and then forward it – Ghulam Mohayudin Jul 27 '17 at 11:37
  • hehehe.......... deffering a request means giving your responsibility to $q.deffer – muhammad hasnain Jul 27 '17 at 11:40
  • 3
    you are talking about angularjs, the post is related to angular 4 – Pasquale Vitale Jul 27 '17 at 12:42
  • https://github.com/NgSculptor/ng2HttpInterceptor/blob/master/src/app/http.interceptor.ts you can refer this document if it helps you – muhammad hasnain Jul 27 '17 at 12:53
  • @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. – Pasquale Vitale Jul 27 '17 at 13:14
  • @pasquale I don't know about angular 4 but just searched for your issue. Look into following thread if it solves your problem. https://stackoverflow.com/questions/44396890/angular-4-http-interceptor – muhammad hasnain Jul 27 '17 at 13:19
  • unfortunately, extends http class is not a adoptable solution in my context, because I need httpClient and its flexible configuration on interceptors – Pasquale Vitale Jul 27 '17 at 13:43