badera
badera

Reputation: 1545

HttpClient Interceptor forces request duplicates

I like to have a HttpInterceptor (Angular 6), which adds Authorization Headers but also handles 401 to redirect to login page. This is my code:

import {Injectable} from '@angular/core';
import {HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
import {Observable} from 'rxjs';
import {Router} from '@angular/router';

@Injectable()
export class JwtInterceptor implements HttpInterceptor {


    constructor(private router: Router) {
    }

    intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        // add authorization header with jwt token if available
        let currentUser = JSON.parse(localStorage.getItem('currentUser'));
        if (currentUser && currentUser.token) {
            request = request.clone({
                setHeaders: {
                    Authorization: `Bearer ${currentUser.token}`,
                },
            });
        }

        const req = next.handle(request);

        // ---------------- VVV ---------------
        req.subscribe(() => {
        }, (error: any) => {
            if (error instanceof HttpErrorResponse && (error as HttpErrorResponse).status === 401)
                this.router.navigate(['public/login']);
        });
        // ---------------- ^^^ ---------------

        return req;
    }
}

All works fine execpt that the code inside the vvv / ^^^ comment enforces that the request is sent twice. Why this? OK, I probably have to subscriptions on the request because this interceptor subscribes and probably my service using the HttpClient. Is there a bether way to solve this?

Edit: Here are the dependencies from package.json:

...
"@angular/compiler": "6.0.3",
"@angular/core": "6.0.3",
"@angular/http": "6.0.3",
"@angular/router": "6.0.3",
"rxjs": "^6.2.0",
"rxjs-compat": "^6.2.0",
"rxjs-tslint": "^0.1.4",
"zone.js": "^0.8.26"
...

Upvotes: 0

Views: 2789

Answers (2)

Ashish Kadam
Ashish Kadam

Reputation: 1487

Install rxjs-compat and Try this, it's work for me.

import { Injectable } from '@angular/core';
import {
  HttpInterceptor,
  HttpHandler,
  HttpRequest,
  HttpEvent,
  HttpResponse,
  HttpErrorResponse
} from '@angular/common/http';
import { Router } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { do } from 'rxjs/operators';

@Injectable()
export class InterceptorService implements HttpInterceptor {
  constructor(private router: Router) {}

  intercept(
    req: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    if (
      localStorage.getItem('JWT-TOKE') !== '' && 
      localStorage.getItem('JWT-TOKE') !== null
    ) {
      const JWT = localStorage.getItem('JWT-TOKE');
      req = req.clone({
        setHeaders: {
          Authorization: 'Bearer ' + JWT
        }
      });
    }
    return next.handle(req).do(
      (event: HttpEvent<any>) => {},
      (err: any) => {
        if (err instanceof HttpErrorResponse) {
          if (err.status === 401) {
            localStorage.clear();
            this.router.navigate(['/login']);
          }
        }
      }
    );
  }
}

Update

If You not using rxjs-compat then use tap instead of do, because of do is reserved keyword in javascript.

import { Injectable } from "@angular/core";
import {
    HttpInterceptor,
    HttpHandler,
    HttpRequest,
    HttpEvent,
    HttpResponse,
    HttpErrorResponse
} from "@angular/common/http";
import { Router } from "@angular/router";
import { Observable } from "rxjs";
import { tap } from "rxjs/operators";
import "rxjs/add/operator/do";

@Injectable()
export class HttpInterceptorService implements HttpInterceptor {
    constructor(private router: Router) {}


    intercept(
        req: HttpRequest<any>,
        next: HttpHandler
    ): Observable<HttpEvent<any>> {
        if (
            localStorage.getItem("jwtToken") !== "" &&
            localStorage.getItem("jwtToken") !== null
        ) {
            const JWT = localStorage.getItem("jwtToken");
            req = req.clone({
                setHeaders: {
                    Authorization: JWT
                }
            });
        }

        return next.handle(req).pipe(
            tap((event: HttpEvent<any>) => {
                console.log(event);
                // handle error here
            }),
            tap((err: any) => {

                console.log(err);
            })
        );

    }
}

Upvotes: 0

Nitishkumar Singh
Nitishkumar Singh

Reputation: 1839

You should use do() instead of subscribe()

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { 
   // add authorization header with jwt token if available
        let currentUser = JSON.parse(localStorage.getItem('currentUser'));
        if (currentUser && currentUser.token) {
            request = request.clone({
                setHeaders: {
                    Authorization: `Bearer ${currentUser.token}`,
                },
            });
        }
  return next.handle(request).do((event: HttpEvent<any>) => {
    if (event instanceof HttpResponse) {
      // do stuff with response if you want
    }
  }, (err: any) => {
    if (err instanceof HttpErrorResponse {
      if (err.status === 401) {
        this.router.navigate(['public/login']);
      }
    }
  });
}

Difference between do() and subscribe()

Edit

import do operator import { do } from 'rxjs/operators';

Why do/tap get ignored when used as subscribe?

The point here is that do() will not affect the flow of the stream, unlike other operators. It takes the response, does something and even if it modifies the response the stream is going to ignore it. When you try to use it as subscribe() it simply get's ignored as you have already returned stream in below statement

Upvotes: 3

Related Questions