Angular 4.3 Interceptors - How to use?

Viewed 9736

I am in the process of building a new app that requires authorization headers. Typically I use something very similar to the approach found in this scotch.io article. But it has come to my attention that HTTP Interceptors are now fully supported within the Angular 4 ecosystem via the new HttpClientModule and I am trying to find some documentation on how exactly to use them.

If I am incorrect in (as of 4.3) this being the best practice for injecting authorization headers, I'd also be open to suggestions. My thinking was that it was a feature added recently which means there is probably good reason to migrate to an "Angular Approved" method.

3 Answers

Injecting AuthService to the Interceptor's constructor was giving me this error:

Uncaught Error: Provider parse errors: Cannot instantiate cyclic dependency! InjectionToken_HTTP_INTERCEPTORS ("[ERROR ->]"): in NgModule AppModule in ./AppModule@-1:-1

So instead of injecting it to the constructor, I used Injector of @angular/core and it worked fine. I am storing the token in localStorage and using basic auth. I need to set

Authorization: 'Bearer token_string'

Here is how I have implemented:

token.interceptor.ts

import {Injectable, Injector} from '@angular/core';

import { HttpRequest, HttpHandler, HttpEvent, HttpInterceptor } from '@angular/common/http';
import {Observable} from 'rxjs/Observable';
import {AuthService} from './auth.service';

@Injectable()
export class TokenInterceptor implements HttpInterceptor {

    constructor(private injector: Injector) { }

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

        const auth = this.injector.get(AuthService);
        if (auth.getToken()) {
            request = request.clone({
                setHeaders: {
                    Authorization: `Bearer ${auth.getToken()}`
                }
            });

        }

        return next.handle(request);
    }
}

getToken function in AuthService

Here you can implement the whole logic to get the header or only the token. Here in my case, I am only calling this to get the JWT token string.

/**
 * Get jwt token
 * @returns {string}
 */
getToken(): string {
    return localStorage.getItem('token');
}

app.module.ts

Import the TokenInterceptor

import {TokenInterceptor} from './pathToTheFile/token.interceptor';

add the following under @NgModule in providers: array.

providers: [
    {
        provide: HTTP_INTERCEPTORS,
        useClass: TokenInterceptor,
        multi: true
    }
    //, other providers
]

The problem I had with the recommended approach, was that the interceptors had to be known at compile time and, apparently, all in the same module.

I opted for implementing one interceptor and a chain of handler functions which can be extended at runtime. The service's intercept method takes care of the next() logic.

The service (basic code, no validations or maintenance):

export type HttpHandlerFunction = (req: HttpRequest<any>) => HttpRequest<any>;

@Injectable()
export class HttpInterceptorService implements HttpInterceptor {
    private _handlers: Array<HttpHandlerFunction> = [];

    addHandler(handler: HttpHandlerFunction): void {
        this._handlers.push(handler);
    }

    intercept(req: HttpRequest<any>, next: HttpHandler):
        Observable<HttpEvent<any>> {
            this._handlers.forEach((handler: HttpHandlerFunction) => {
                req = handler(req);
            })
        return next.handle(req);
    }
}

And the usage, in some service in a different module:

constructor(injector: Injector) {

    const interceptorsArray: Array<any> = injector.get(HTTP_INTERCEPTORS),
        interceptor: HttpInterceptorService = interceptorsArray &&
            interceptorsArray.filter((i: any) => i instanceof HttpInterceptorService)[0];
    if (interceptor) {
        interceptor.addHandler((req: HttpRequest<any>) => {
            const accessToken = this.getAccessToken();
            if (accessToken) {
                // doesn't work with direct headers.set, you must clone
                req = req.clone({ headers: req.headers.set('Authorization', accessToken) });
            }
        return req;
    });
}
Related