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);
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);
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());
}
}
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()))
}
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);
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
}
to the httpClient istance.
bootstrapApplication(AppComponent, {
providers: [ provideHttpClient(
withInterceptorsFromDi())
]
}).catch(console.error);
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)
Can you provide an example how to test interceptor functions with Jest and ideally ngMocks, please?
Of course, i willl cover this topic in my next article, that will be published on wednesday, stay tuned :)
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.
Figured it out
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.
Hello, sorry for this mistake and thanks for telling me, this is fixed now