Angular 15 introduces functional HTTP interceptors
Less boilerplate and more tree-shakable. Let's compare.
Angular continues on its recent trend to improve developer experience and reduce boilerplate by welcoming HTTP interceptors, and client, on the functional train. This article will go over what’s new and why you’d want to jump on it too.
To learn about using a similar pattern to simplify your route guards, take a look at our friend Kate’s article, Functional router guards in Angular 15 open the door to happier code.
Angular is getting leaner and so HttpClient
Angular’s packages are undergoing significant API refactoring to make them more extensible and tree-shakable, including @angular/common/http. The preferred way to configure the router in your app, module, or component is now the provideHttpClient() function.
This function supersedes HttpClientModule and returns the set of providers your app needs with just the specified features.
One of these new HttpFeatures is withInterceptors(): the new way to add interceptors to your HttpClient. Pass in a list of HttpInterceptorFns to configure your interceptor pipeline, similar to the way it used to be done.
Did you say HttpInterceptorFn?
That’s right: going forward, you can write functional interceptors. This means class boilerplate isn’t required anymore thanks to several factors. Let’s compare.
The HttpInterceptor interface only required one method be implemented, so the only reason we were writing classes for this was dependency injection; not only the type of DI we define in our classes, but the logic going on under the hood in the HttpClient. With the inject function now usable in more contexts, lots of functionality can be simplified, including these.
This is a functional interceptor in its simplest form that will do something with the request and return it to the handler. HttpInterceptorFn types the arguments and return value, so multiple type imports are simplified into the one. Services or other providers needed for the logic can be injected, too. The second argument, next, was also changed to the simpler HttpHandlerFn that's just called directly. Use it by adding it to the interceptors feature in your provided HttpClient:
Classes vs. functions, or inheritance vs. composition
In the vast majority of cases, functions should be preferred over classes and inheritance. I won’t go too deep into this subject, but composition unlocks advanced patterns and promotes reusability. A great example of this is higher-order functions, and interceptors can take advantage of it.
Interceptors from parent HttpClients
Another neat feature they’ve added is the ability to inherit interceptors from parent injectors. A lazy loaded module creates a separate environment injector that can provide its own HttpClient but have requests routed through its parent clients and their interceptors when withRequestsMadeViaParent is configured.
What happens with existing class-based interceptors?
One of the things the Angular team does best is create a migration path for large-scale changes. If a project has any number of class interceptors that can’t be refactor immediately, they’ve exposed a feature called withInterceptorsFromDi() that'll inject HTTP_INTERCEPTORS provided “the old way”. You can use it in isolation or combine it with functional interceptors.
Other newly configurable features
Some other features worth mentioning are XSRF configuration and JSONP support. These already existed with the original HttpClientModule, in the form of extra module imports HttpClientXsrfModule and HttpClientJsonpModule, but are now also provided in the nicer (and more tree-shakable) feature function way.
Wrap-up
There’re many ways to improve DX, and these features definitely help in many of them. Smaller bundles, better types, more readable code, and more powerful functionality will always be well-received by everyone. HttpClient, Router, Forms, and Angular as a whole are getting better every day, and I can't wait to see what's next!