Angular – New HTTP Interface With Interceptors

Jul 19, 2017 reading time 9 minutes

Angular – New HTTP Interface With Interceptors

In this blog post I want to explore the latest HTTP interface from angular which was introduced in Angular 4.3.

We all need to get our data from any source, mostly this is done via HTTP and any REST backend (like node or ASP.NET Core etc.) or even from files in the *.json format. However, this was possible ever since and is one of the key features of an SPA but Angular introduced a improved version of the HTTP Api in version 4.3. Let’s take a look into this one.

Imports // Module

The new module is provided via the HttpClientModule interface. So you have to import it first in your module.

Change

import { HttpModule } from '@angular/http';

to

import { HttpClientModule } from '@angular/common/http';

// ...
import { HttpClientModule } from '@angular/common/http';
// ...

@NgModule({
    imports: [
        // ...
        HttpClientModule,
        // ...
    ],

    declarations: [ ... ],

    providers: [ ... ],

    exports: [ ... ]
})

export class HomeModule { }

Dependency Injection // Constructors

Now you wil notice that you do not have a “provider for Http” anymore. So we have to change that as well:

So everywhere you inject

constructor(private http: Http) { }

You have to change that to

import { HttpClient } from '@angular/common/http';
// ...
constructor(private http: HttpClient) { }

Methods // HTTP Verbs

We can use the HTTP methods around GET, POST etc. in a new way. The .json() is not necessary anymore. JSON is now assumed as a standard. So that means, we can get rid of the first .map((response: Response) => <MyType>response.json()) method. In addition to that the method now are generic which means that they can take the type directly and you do not need to cast it anymore. So from

public get(): Observable<MyType> => {
    return this.http.get(url)
        .map((response: Response) => <MyType>response.json());
}

to

get<T>(url: string): Observable<T> {
    return this.http.get<T>(url);
}

or for a post request its pretty much straight forward as well

post<T>(url: string, body: string): Observable<T> {
    return this.http.post<T>(url, body);
}

Headers

To apply headers we can add another parameter to the get function passing an object with a headers property.

So

get<T>(url: string): Observable<T> {
    return this.http.get<T>(url);
}

post<T>(url: string, body: string): Observable<T> {
    return this.http.post<T>(url, body);
}

becomes to

get<T>(url: string, headers?: HttpHeaders | null): Observable<T> {
    const expandedHeaders = this.prepareHeader(headers);
    return this.http.get<T>(url, expandedHeaders);
}

post<T>(url: string, body: string, headers?: HttpHeaders | null): Observable<T> {
    const expandedHeaders = this.prepareHeader(headers);
    return this.http.post<T>(url, body, expandedHeaders);
}

//...

private prepareHeader(headers: HttpHeaders | null): object {
    headers = headers || new HttpHeaders();

    headers = headers.set('Content-Type', 'application/json');
    headers = headers.set('Accept', 'application/json');

    return {
        headers: headers
    }
}

Be aware that setting a new header returns the new header object instead of manipulation your existing one. Its immutable.

Interceptors

From AngularJS we know HTTP interceptors as a great and very mighty way to observe ingoing and outgoing requests. In Angular this feature of interceptors was not available yet. But it came back with this new version of the http interface.

Let us add an interceptor which is logging the requests. This is a class tagged with the @Injectable decorator implementing and interface HttpInterceptor. It gets passed an HttpHeader which provides us the handle(...) method to not cancel the request but hook ‘between’ the application and the outgoing or incoming request. So we have to give the request further to be handled from the enxt interceptor (chaining) or the backend.

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

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

and of course we have ot reigster it on the module

providers: [
  // ...
  {
    provide: HTTP_INTERCEPTORS,
    useClass: MyFirstInterceptor,
    multi: true,
  },
];

If we log this one we get a result like this

{
	"url": "http://blabla",
	"body": null,
	"reportProgress": false,
	"withCredentials": false,
	"responseType": "json",
	"method": "GET",
	"headers": {
		"normalizedNames": {},
		"lazyUpdate": null,
		"headers": {}
	},
	"params": {
		"updates": null,
		"cloneFrom": null,
		"encoder": {},
		"map": null
	},
	"urlWithParams": "http://blablawithparams"
}

Now we can tweak the interceptor to add the headers accordingly and add the authentication token if the user has one.

@Injectable()
export class MyFirstInterceptor implements HttpInterceptor {

    constructor(private currentUserService: CurrentUserService) { }

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

        // get the token from a service
        const token: string = this.currentUserService.token;

        // add it if we have one
        if (token) {
            req = req.clone({ headers: req.headers.set('Authorization', 'Bearer ' + token) });
        }

        // if this is a login-request the header is
        // already set to x/www/formurl/encoded.
        // so if we already have a content-type, do not
        // set it, but if we don't have one, set it to
        // default --> json
        if (!req.headers.has('Content-Type')) {
            req = req.clone({ headers: req.headers.set('Content-Type', 'application/json') });
        }

        // setting the accept header
        req = req.clone({ headers: req.headers.set('Accept', 'application/json') });
        return next.handle(req);
    }
}

With this we can get rid of the method in the class above and we dont need to call the method private prepareHeader(headers: HttpHeaders | null): object to prepare the headers. So the final result could be like:

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

import { CurrentUserService } from '../services/currentUser.service';

@Injectable()
export class MyDataService {
    constructor(private http: HttpClient, private currentUserService: CurrentUserService) { }

    get<T>(url: string): Observable<T> {
        return this.http.get<T>(url);
    }

    post<T>(url: string, body: string): Observable<T> {
        return this.http.post<T>(url, body);
    }

    put<T>(url: string, body: string): Observable<T> {
        return this.http.put<T>(url, body);
    }

    delete<T>(url: string): Observable<T> {
        return this.http.delete<T>(url);
    }

    patch<T>(url: string, body: string): Observable<T> {
        return this.http.patch<T>(url, body);
    }
}


@Injectable()
export class MyFirstInterceptor implements HttpInterceptor {

    constructor(private currentUserService: CurrentUserService) { }

    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        console.log(JSON.stringify(req));

        const token: string = this.currentUserService.token;

        if (token) {
            req = req.clone({ headers: req.headers.set('Authorization', 'Bearer ' + token) });
        }

        if (!req.headers.has('Content-Type')) {
            req = req.clone({ headers: req.headers.set('Content-Type', 'application/json') });
        }

        req = req.clone({ headers: req.headers.set('Accept', 'application/json') });
        return next.handle(req);
    }
}

I hope with this I got you a basic understanding of the new neat HTTP api from Angular. I like it a lot and this code looks nicer and cleaner that way.

BR

Fabian