DEV Community

seanbh
seanbh

Posted on • Originally published at Medium on

How to Create a Functional Interceptor in Angular


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);
  }
}
Enter fullscreen mode Exit fullscreen mode

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);
};
Enter fullscreen mode Exit fullscreen mode

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 },
],
Enter fullscreen mode Exit fullscreen mode

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]))],
Enter fullscreen mode Exit fullscreen mode

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.

Bibliography

Top comments (2)

Collapse
 
0521332 profile image
Exorcism0546

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?

Collapse
 
seanbh profile image
seanbh

Hi, thanks!

You can inject anything that you would normally put in a class constructor by using inject() in the body of the function:

const store = inject(Store);
const appConfig = inject(APP_CONFIG);
const globalErrorHandlerService = inject(GlobalErrorHandlerService);
Enter fullscreen mode Exit fullscreen mode

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:

import { HttpRequest, HttpHandlerFn, HttpEvent } from '@angular/common/http';
import { Observable } from 'rxjs';

export const authInterceptor = (myToken: string) => {
  return (request: HttpRequest<unknown>, next: HttpHandlerFn): Observable<HttpEvent<unknown>> => {
    const clonedRequest = request.clone({
      setHeaders: {
        Authorization: `Bearer ${myToken}}`,
      },
    });
    return next(clonedRequest);
  };
};
Enter fullscreen mode Exit fullscreen mode