Photo by Chris Moore on Unsplash
Angular is moving towards functional interceptors, rather than class-based ones (the same is true of guards). Note this statement in the docs:
Prefer…functional interceptors instead, as support for DI-provided interceptors may be phased out in a later release.
What is the difference between a class-based interceptor and a functional one? That is what we are going to explore in this post.
I am going to compare and contrast class-based and functional interceptors in this post, but feel free to skip straight to the functional implementation if that is all you are interested in.
The Interceptor
Class-Based Version
A simple class-based interceptor that adds an Authorization header to all HTTP requests would look something like this:
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
intercept(
request: HttpRequest<unknown>,
next: HttpHandler
): Observable<HttpEvent<unknown>> {
const clonedRequest = request.clone({
setHeaders: {
Authorization: 'Bearer [the token]',
},
});
return next.handle(clonedRequest);
}
}
The interceptor is just a class that implements the HttpInterceptor interface and participates in dependency injection. The intercept method is the only method implemented and it takes the intercepted request and the next handler, which allows the request to be passed on through the pipeline.
This interceptor is simply cloning the request and setting an Authorization header with a fake Bearer token. It then hands the request off to the next handler (if you want to see a more realistic example of adding an Authorization header you can look at my previous post).
Functional Version
A functional version of this simple interceptor would look like this:
import {
HttpRequest,
HttpInterceptorFn,
HttpHandlerFn,
HttpEvent,
} from '@angular/common/http';
import { Observable } from 'rxjs';
export const authInterceptor: HttpInterceptorFn = (
request: HttpRequest<unknown>,
next: HttpHandlerFn
): Observable<HttpEvent<unknown>> => {
const clonedRequest = request.clone({
setHeaders: {
Authorization: 'Bearer [the token]',
},
});
return next(clonedRequest);
};
What changes do we see?
- Obviously, the biggest change is that we’ve made the interceptor a constant function (of type HttpInterceptorFn) rather than an injectable _class — _hence the interceptor is now functional rather than class-based.
- The function still takes the same request parameter, but the next handler is now a function rather than a class. As such, there is no handle method on next — rather we just invoke next() and pass in the cloned request.
Here are the differences side by side:
The code difference between the class-based and functional interceptors
Providing the Interceptor
The differences in the interceptor are pretty minimal in my opinion. What may be a little more challenging at first is understanding the shift in how we provide the interceptor.
Class-Based
We provide a class-based interceptor in the app module like this:
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
...
...
imports: [
HttpClientModule,
...
...
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
],
Notice that I’ve included the import of the HttpClientModule, not because it is required for the interceptor but because it is required to actually make HTTP requests — or at least it used to be. The way we are going to provide the new functional interceptor also changes the way we gain access to HttpClient.
Note that there is a backwards compatible way to still use the HttpClientModule with a functional interceptor, but I wouldn’t recommend it unless you just need it for a temporary solution until you’re able to fully migrate.
Functional
As part of the shift to standalone, Angular now has so-called standalone APIs that allow for building an application without modules. So, when we provide our new functional interceptor, we are going to remove the HttpClientModule completely, and use the standalone API to provide both the interceptor and HttpClient:
// HttpClientModule was removed from imports
providers: [provideHttpClient(withInterceptors([authInterceptor]))],
The provideHttpClient function is all we need to make HttpClient available in the application. We pass in the withInterceptors function to provide authInterceptor at the same time.
Here’s the diff between the module/class and standalone/functional approaches:
The code difference between the module/class and standalone/functional approaches
That’s it! I hope you found this useful.
If you want to see how to unit test the functional interceptor we implemented here, check out this post.
Top comments (2)
Interesting post!
How would you inject parameters/services to your function with this way though? Or it just doesn't make sense to use a function in this case and you would stick with a class?
Hi, thanks!
You can inject anything that you would normally put in a class constructor by using inject() in the body of the function:
So, you'd probably get the actual token from a store or service.
If you want to pass parameters when you call the function, you would have to wrap it in an outer function: