DEV Community

Cover image for Angular 15: interceptor as a function
Nicolas Frizzarin for This is Angular

Posted on • Updated on

Angular 15: interceptor as a function

Introduction

The release of Angular 14 made it possible to use the inject function outside the injection context. This has unlocked some features like:

  • the composition pattern
  • creation of guards as a simple function

With the release of Angular 15, it is around the interceptors to benefit from this writing.
However, the interceptors are linked to the httpClient instance and with the introduction of optional modules several questions arise:

  • how to register now the http client?
  • how to write an interceptor as a simple function?
  • how to register it?
  • is it possible to have interceptors as class and interceptors as functions in a same application?

The new way to register the http client

Before the release of Angular 15, if we wanted to bootstrap on a standalone component and provide the http client of Angular, we had to use the importProvidersFrom(module) function which take care of:

  • importing the providers exported by the module
  • registering the providers in the environment injector
bootstrapApplication(AppComponent, {
  providers: [ importProvidersFrom(HttpClientModule) ]
}).catch(console.error);
Enter fullscreen mode Exit fullscreen mode

Now with Angular 15, Angular exposes a new function called provideHttpClient which allows to simply register the http client.
This function takes as parameter a list of HttpFeature. A concrete example of HttpFeature is the registration of interceptors.

bootstrapApplication(AppComponent, {
  providers: [ provideHttpClient() ]
}).catch(console.error);
Enter fullscreen mode Exit fullscreen mode

The new way to write interceptor and to register them

Before Angular 15, an interceptor was always a class preceded by the @Injectable annotation.
This writing was very useful because it allowed us to inject our providers.

@Injectable()
export class AuthorizationInterceptorDI implements HttpInterceptor {
  constructor(private loadingService: LoadingService){}
  intercept(request: HttpRequest<any>>, next: HttpHandler): Observable<HttpEvent<any> {
    this.loadingService.startLoader();
    const clonedRequest = request.clone({ setHeaders: { Authorization: 'this_is_angular' } });
    return next.handle(clonedRequest)
      .pipe(finalize(() => this.loadingService.stopLoader());
  }
}
Enter fullscreen mode Exit fullscreen mode

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

export function AuthorizationInterceptor(req: HttpRequest<unknown>,
next: HttpHandlerFn){
    const loadingService = inject(LoadingService);
    loadingService.startLoader();
    const clonedRequest = request.clone({ setHeaders: { 
      Authorization: 'this_is_angular' } });
    return next(clonedRequest)
      .pipe(finalize(() => this.loadingService.stopLoader()))
}
Enter fullscreen mode Exit fullscreen mode

An interceptor function takes two parameters

  • the request on which the interceptor will interact
  • a function that allows to send the transformed request

The return of this function must be an observable of type HttpEvent to be able to interact with the response of the http request.

To add an interceptor function in the httpClient instance, Angular gives us a new withInterceptors function.

This function takes as parameter an array of interceptors function and returns an HttpFeature of type interceptor.
This type of return is very useful because it allows later to call this function in the provideHttpClient function.

bootstrapApplication(AppComponent, {
  providers: [ provideHttpClient(
    withInterceptors([AuthorizationInterceptor])) 
  ]
}).catch(console.error);
Enter fullscreen mode Exit fullscreen mode

There are many other functions that return an HttpFeature. It is the case for example of the functions

  • withXsrfConfiguration which allows the customization of the XSRF protection
  • withJsonpSupport which allows the support of JSONP in Http requests

More information here.

Is it possible to mix ?

In an Angular application, it is now possible to have standalone components and modules. This cohabitation can be very practical if you want to migrate step by step an application based on modules to an application based only on standalone components.

In this idea, if our application bootstrap now on a standalone components and so we use the provideHttpClient function to register the http client, is it necessary to migrate all interceptors to the new function format?

The answer is ... NO. It is possible to make interceptors in function format and interceptors in class format coexist.
To do this Angular provides us with a special function: withInterceptorsFromDi

The withInterceptorsFromDi function is intended to add the interceptors registered in the old format:

{ provide: HTTP_INTERCEPTORS,
  useClass: AuthorizationInterceptorDI
  multi: true
}
Enter fullscreen mode Exit fullscreen mode

to the httpClient istance.

bootstrapApplication(AppComponent, {
  providers: [ provideHttpClient(
    withInterceptorsFromDi()) 
  ]
}).catch(console.error);
Enter fullscreen mode Exit fullscreen mode

Thanks to these two distinct functions, it is possible to make the two syntaxes cohabit within the same Angular application, and thus allow a progressive migration.

Conclusion

Angular 15 allows the stabilization of standalone components and allows to continue the removal of boilerplating.
The new syntax of interceptors in function format is lighter than the previous one.

Thanks to the functions withInterceptors and withInterceptorsFromDi, the interceptors can be migrated step by step.

Nevertheless, prefer from now on the syntax in function format and the use of withInterceptors because the function withInterceptorsFromDi risks to be removed in the future versions of Angular.

Top comments (6)

Collapse
 
mvonfrie profile image
Marco von Frieling

Can you provide an example how to test interceptor functions with Jest and ideally ngMocks, please?

Collapse
 
nicoss54 profile image
Nicolas Frizzarin

Of course, i willl cover this topic in my next article, that will be published on wednesday, stay tuned :)

Collapse
 
spock123 profile image
Lars Rye Jeppesen

Thanks for the example.
Unfortunately this is yet another outgoing interceptor example.
Incoming examples would be nice but I've yet to come across a functional incoming interceptor.

Collapse
 
spock123 profile image
Lars Rye Jeppesen

Figured it out

Collapse
 
ethanstandel profile image
Ethan Standel • Edited

Your function used invalid syntax. You would need function or const after export (and if you used const then you'd have to turn the function into an assigned arrow or use the function keyword again.

Collapse
 
nicoss54 profile image
Nicolas Frizzarin

Hello, sorry for this mistake and thanks for telling me, this is fixed now