Angular: Functional Interceptors

Antonio Anderson
4 min readOct 5, 2023

--

São Paulo at night by Maria Globetrotter

Explore the latest Angular features, with a deep dive into functional interceptors and their game-changing capabilities.

Angular, the popular front-end framework, has just released version 15, bringing with it a host of exciting features and enhancements. Among these, functional interceptors stand out as a powerful tool that can simplify and improve the way we handle HTTP requests and responses in Angular applications.

In this article, we’ll take you on a journey through the world of Angular 15’s functional interceptors. We’ll cover the following key points:

Registering the HttpClient with Ease

With the introduction of Angular 15, registering the HttpClient has never been easier. We’ll show you how to use the provideHttpClient() function to streamline this process, saving you time and effort in your application setup. Before the release of Angular 15, if we wanted to bootstrap into a standalone component and provide Angular’s http client, we would have to use the `importProvidersFrom(module)` function which cost:

  • importing providers exported by the module
  • registering providers in the injector environment
bootstrapApplication(AppCmp, {
providers[importProvidersFrom(HttpClientModule)]
}).catch(console.error)

Now, in the version 15, a function called provideHttpClient() has been added that allows you to simply register the httpClient.

boostrapApplication(AppCmp, {
providers: [provideHttpClient()],
});

Embracing Functional Interceptors

Traditionally, Angular interceptors were implemented as classes. However, Angular 15 introduces a groundbreaking change: the ability to write interceptors as functions. We’ll walk you through this transition and demonstrate how it can lead to more concise and maintainable code.

import {
HttpErrorResponse,
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest,
} from "@angular/common/http";
import { Observable, throwError } from "rxjs";
import { catchError } from "rxjs/operators";

export class HttpErrorInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req).pipe(
catchError((error: HttpErrorResponse) => {
let errorMsg = "";
if (error.error instanceof ErrorEvent) {
console.log("this is client side error");
errorMsg = `Error: ${error.error.message}`;
} else {
console.log(`this is serve side error`);
errorMsg = `Error Code: ${error.status}, Message: ${error.message}`;
}
console.log(errorMsg);
return throwError(() => errorMsg);
}),
);
}
}

Thanks to the fact that the ‘inject’ function can be used outside the context of dependency injection, this class can be transformed into a function.

import {
HttpErrorResponse,
HttpHandlerFn,
HttpInterceptorFn,
HttpRequest,
} from "@angular/common/http";
import { catchError, throwError } from "rxjs";

export const httpErrorInterceptor: HttpInterceptorFn = (
req: HttpRequest<unknown>,
next: HttpHandlerFn,
) => {
return next(req).pipe(
catchError((error: HttpErrorResponse) => {
let errorMsg = "";
if (error.error instanceof ErrorEvent) {
console.log("this is client side error");
errorMsg = `Client Error: ${error.error.message}`;
} else {
console.log("this is server side error");
errorMsg = `Server Error Code: ${error.status}, Message: ${error.message}`;
}

console.log(errorMsg);
return throwError(() => errorMsg);
}),
);
};

An interceptor function takes two parameters:

  • The request with which the interceptor will interact.
  • A function that allows you to send the transformed request.

The return value of this function should be an Observable<HttpEvent<any>>, of type HttpEvent, to effectively interact with the HTTP request’s response.

To add an interceptor function to the HttpClient instance, a new function called withInterceptors() is provided. This function takes an array of interceptor functions as its parameter and returns an HttpFeature of the interceptor type. This return type is highly useful as it allows you to subsequently invoke the function in the provideHttpClient() function.

import { httpErrorInterceptor } from "@/interceptors";

boostrapApplication(AppCmp, {
providers: [provideHttpClient(withInterceptors([HttpErrorInterceptor]))],
}).catch(console.error);

Coexistence of Class-Based and Functional Interceptors

One of the unique features of Angular 15 is the ability to have both class-based and functional interceptors coexisting in your application. We’ll introduce you to the withInterceptorsFromDi() function, which allows you to seamlessly integrate these two types of interceptors.

In an Angular application, it is now possible to have independent components and modules coexisting. This cohabitation can be highly practical, especially if you wish to gradually migrate an application from a module-based structure to one that relies solely on autonomous components.

In this scenario, if your application is now initialized using standalone components and you utilize the provideHttpClient function to register the HttpClient, do you need to migrate all the interceptors to the new function-based format?

The answer is no. It is possible to have both function-based and class-based interceptors coexist. To achieve this, Angular provides us with a special function called withInterceptorsFromDi().

The purpose of the withInterceptorsFromDi() function is to add interceptors that are registered in the old format, like this:

  {
provide: HTTP_INTERCEPTORS,
useClass: LegacyInterceptor,
multi: true
}

to the HttpClient instance like this:

  bootstrapApplication(AppCmp, {
providers: [provideHttpClient(withInterceptorsFromDi())]
}).catch(console.error)

This approach allows for a smooth transition when adopting the new functional interceptor format while still accommodating existing class-based interceptors.

Future-Proofing Your Angular Application

We’ll conclude by emphasizing the advantages of adopting the functional interceptor syntax and provide insights into why you should consider making the switch. We’ll also caution against potential deprecation of certain functions in future Angular releases, ensuring that your application remains future-proof.

If you’re an Angular developer looking to stay ahead of the curve and optimize your application’s HTTP request handling, this article is a must-read. Join us as we unlock the power of Angular 15’s functional interceptors and elevate your Angular development skills. Don’t miss out on the future of Angular development — read our article today!

You can see the source code example: Github

--

--

Antonio Anderson

Antonio is a Brazilian Software engineer based in São Paulo. Talks about Software engineering!