Migrate Angular Interceptors to function based interceptors

Enea Jahollari
ITNEXT
Published in
4 min readMay 13, 2023

--

Photo by Patrick Hendry on Unsplash

In this article we will learn how to migrate class based interceptors to function based interceptors. In Angular v15, the Angular team introduced a new way to create interceptors. Instead of creating a class that implements
the HttpInterceptor interface, we can now create a function that implements the HttpInterceptorFn interface.

Why should we migrate to function based interceptors?

Class based interceptors are great, but they may be phased out in a later release. This is taken from here: withInterceptorsFromDi() 😬

Other than that, function based interceptors are better than class based interceptors for many reasons:

  • Easier to create
  • Easier to configure
  • Easier to use

Migrate RetryInterceptor to function based interceptor

In my previous article 👇

We created a configurable retry interceptor. Let’s incrementally migrate that one to functional based interceptors and see what we gain from it.

Our interceptor looks like this:

export const RETRY_INTERCEPTOR_CONFIG = new InjectionToken<RetryConfig>(
'retryConfig',
{
providedIn: 'root',
factory: () => {
return { count: 3, delay: 1000 } as RetryConfig;
},
}
);

@Injectable()
export class RetryInterceptor implements HttpInterceptor {
private retryConfig = inject(RETRY_INTERCEPTOR_CONFIG);

intercept(request: HttpRequest<unknown>, next: HttpHandler) {
return next.handle(request).pipe(retry(this.retryConfig));
}
}

export const RetryInterceptorProvider = {
provide: HTTP_INTERCEPTORS,
useClass: RetryInterceptor,
multi: true,
};

How to migrate to function based interceptors?

To migrate to function based interceptors, first we need to do something else first. We need to use provideHttpClient instead of HttpClientModule.

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

@NgModule({
imports: [
- HttpClientModule,
],
providers: [
+ provideHttpClient(),
RetryInterceptorProvider,
{
provide: RETRY_INTERCEPTOR_CONFIG,
useValue: { count: 5, delay: 2000 },
},
],
})
export class AppModule {}

We’re not done yet. When using provideHttpClient, and if we still are using class based interceptors, we need to use the withInterceptorsFromDi function to register the class based interceptors.

import { provideHttpClient, withInterceptorsFromDi } from '@angular/common/http';

@NgModule({
providers: [
provideHttpClient(
+ withInterceptorsFromDi()
),
RetryInterceptorProvider,
],
})
export class AppModule {}

Now, we’re in the same state as we were before. We have a class based interceptor, and we’re using the withInterceptorsFromDi function to register it.

How to create a function based interceptor and how to use it?

To better understand the migration process, let’s create a simple functional interceptor first.

import { HttpHandlerFn, HttpInterceptorFn, HttpRequest } from '@angular/common/http';

export const simpleInterceptor: HttpInterceptorFn = (
req: HttpRequest<unknown>,
next: HttpHandlerFn
) => {
return next(req);
};

And we can use it like this:

@NgModule({
providers: [
provideHttpClient(
withInterceptors([simpleInterceptor])
),
],
})
export class AppModule {}

or in a standalone application:

bootstrapApplication(AppComponent, {
providers: [
provideHttpClient(
withInterceptors([simpleInterceptor])
),
]
});

That’s it! We have a simple functional interceptor that we can use in our application.

How to migrate the RetryInterceptor to a function based interceptor?

Now that we know how to create and use a function based interceptor, let’s migrate the RetryInterceptor to a function based interceptor.

Because our interceptor is configurable, we need to pass the configuration to the interceptor.

How can we do that?

We can create a function that creates the interceptor and pass the configuration to it.

import { HttpHandlerFn, HttpRequest } from '@angular/common/http';
import { retry, RetryConfig } from 'rxjs';

export const retryInterceptor = (config: RetryConfig) => {
return (req: HttpRequest<unknown>, next: HttpHandlerFn) => {
return next(req).pipe(retry(config));
};
};

Or, in order to also have better type-checking, let’s change it a bit!

import { HttpHandlerFn, HttpInterceptorFn, HttpRequest } from '@angular/common/http';
import { retry, RetryConfig } from 'rxjs';

export const retryInterceptor = (config: RetryConfig) => {
const interceptor: HttpInterceptorFn = (req: HttpRequest<unknown>, next: HttpHandlerFn) => {
return next(req).pipe(retry(config));
};

return interceptor;
};

Now, we can use it like this:

bootstrapApplication(AppComponent, {
providers: [
provideHttpClient(
withInterceptors([ retryInterceptor({ count: 5 }) ])
),
]
});

That’s it!

Before vs After 🍓

As you can see for yourself, the difference is huge 🚀!

Here’s the playground for the function based interceptor:

If your interceptor depends on other services, you can inject them in the function that creates the interceptor.

For example, if we want to log the requests, we can create an interceptor that injects the logger service.

export const loggerInterceptor: HttpInterceptorFn = (req, next) => {
const loggerService = inject(LoggerService); // inject the logger service

return next(req).pipe(
tap({
next: () => loggerService.log('Request sent'),
error: () => loggerService.error('Request failed'),
complete: () => loggerService.log('Request completed'),
})
);
};

Thanks for reading!

I tweet and blog a lot about Angular (latest news, signals, videos, podcasts, updates, RFCs, pull requests and so much more). 💎

If this article was interesting and useful to you, and you want to learn more about Angular, give me a follow at @Enea_Jahollari or Medium. 📖

I’d appreciate it if you would support me by buying me a coffee ☕️. Thank you in advance 🙌

--

--

GDE for Angular | Software Engineer @ push-based.io | Performance & architecture enthusiast