Angular 4.3 Interceptors - How to use?
Asked Answered
S

3

8

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.

Substantialize answered 27/7, 2017 at 16:0 Comment(4)
blog.ninja-squad.com/2017/07/17/http-client-module this seems usefulActinouranium
You can check github.com/auth0/angular2-jwt/tree/v1.0 1.x branch as the example.Contraoctave
The official angular documentation is pretty good: angular.io/guide/http#setting-new-headersBenis
@Benis How did I miss that? Must be a new section? I even tried to find it before tapping out this question. Thanks so much!Substantialize
A
10

This answer is borrowing from the official documentation linked to by CodeWarrior.

Angular allows you to create an HttpInterceptor:

import {Injectable} from '@angular/core';
import {HttpEvent, HttpInterceptor, HttpHandler, HttpRequest} from '@angular/common/http';

@Injectable()
export class NoopInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(req);
  }
}

which you can then integrate into your app like so:

import {NgModule} from '@angular/core';
import {HTTP_INTERCEPTORS} from '@angular/common/http';

@NgModule({
  providers: [{
    provide: HTTP_INTERCEPTORS,
    useClass: NoopInterceptor,
    multi: true,
  }],
})
export class AppModule {}

To add an authorization header, you can clone the request with the changed headers:

import {Injectable} from '@angular/core';
import {HttpEvent, HttpInterceptor, HttpHandler, HttpRequest} from '@angular/common/http';

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

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    // Get the auth header from the service.
    const authHeader = this.auth.getAuthorizationHeader();
    // Clone the request to add the new header.
    const authReq = req.clone({headers: req.headers.set('Authorization', authHeader)});
    // Pass on the cloned request instead of the original request.
    return next.handle(authReq);
  }
}

Note that the interceptors act like a chain, so you can set up multiple interceptors to perform different tasks.

Actinouranium answered 28/7, 2017 at 1:2 Comment(7)
why is the clone for? why not just set the headers directly in the req with rew.headers.set ?Jeanne
Dag, according to the documentation, the headers object is immutable, so if you want to make changes to it, you have to clone it.Actinouranium
is it the same in angular 4.2.3 ?Pentothal
@bharathmuppa The 4.3.2 (I'm assuming this is what you meant) documentation is unchanged from the examples above.Actinouranium
@john I am referring to 4.2.x, because when I am using interceptor js interpreter executing till interceptor function while loading, but when i call the http service interceptor function is not being executed.Pentothal
@bharathmuppa I couldn't find specific documentation, so I asked on their Gitter channel and I've received confirmation that it's 4.3 upwards.Actinouranium
@john angular cli is coming with 4.2.x so can i move to 4.3 or 4.4 as it is still RC?Pentothal
N
3

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
]
Nix answered 29/1, 2018 at 15:40 Comment(0)
S
0

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;
    });
}
Seashore answered 23/7, 2018 at 12:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.