Another update, or maybe it was there but buried deep in documentation, to allow us to use HttpClient Module-less in Angular. The new function provided is provideHttpClient
. So let’s revisit our StackBlitz project and apply this new feature.
anchorAdding HTTP Client with interceptors
With modules, it looks like this. I added in a couple of interceptors to the project as usual (class-based).
// Http providers array fed to root application, standalone or not
export const HttpProviders = [
importProvidersFrom(HttpClientModule),
// A couple of interceptors
{
provide: HTTP_INTERCEPTORS,
useClass: LocalInterceptor,
multi: true
},
{
provide: HTTP_INTERCEPTORS,
useClass: GarageInterceptor,
multi: true
}
];
To make the transition simpler, Angular provides a function that allows these class-based interceptors as are, using withInterceptorsFromDi
// change into standalone module-less HTTPClient
export const HttpProviders = [
provideHttpClient(
// do this, to keep using your class-based interceptors.
withInterceptorsFromDi()
),
// A couple of interceptors
{
provide: HTTP_INTERCEPTORS,
useClass: LocalInterceptor,
multi: true
},
{
provide: HTTP_INTERCEPTORS,
useClass: GarageInterceptor,
multi: true
}
];
The HttpProviders
is supplied to the root providers in main.ts
, or AppModule
if used.
// main.ts
bootstrapApplication(AppComponent, {
providers: [
// ...
// lets add http providers
...HttpProviders,
],
});
Running this in StackBlitz, I can see the interceptors worked well. Now lets move to Angular’s suggestion: withInterceptors
Prefer withInterceptors
and functional interceptors instead, as support for DI-provided interceptors may be phased out in a later release—Per documentation.
anchorUsing functional interceptor
The other method is to change the interceptor classes into functions of type: HttpInterceptorFn
// pass the interceptor functions to the HTTP providers:
export const HttpProviders = [
provideHttpClient(
// do this, to change to functional interceptors
withInterceptors([
LocalInterceptorFn,
GarageInterceptorFn
])
)
];
The functions look like this:
// local interceptor function
// notice it exports a function of type HttpInterceptorFn
// also take note of the signature, it uses HttpHandlerFn new function
export const LocalInterceptorFn: HttpInterceptorFn = (
req: HttpRequest<any>,
next: HttpHandlerFn
) => {
if (req.url.indexOf('localdata') < 0) {
// notice the direct use of `next` without `.handle()`
return next(req);
}
// do something with url, then handle
let url = req.url;
console.log('url from local interceptor', url);
const adjustedReq = req.clone({ url: url });
return next(adjustedReq);
}
anchorInjecting a service
Do you inject a service like the Loader service? You can use inject
function to continue to use it. The inject function, not the Inject decorator).
// http interceptor function with an injected service
export const GarageInterceptorFn: HttpInterceptorFn = (
req: HttpRequest<any>,
next: HttpHandlerFn
) => {
// do you inject a client service? it goes like this
// import { inject } from '@angular/core';
const loaderService = inject(LoaderService);
loaderService.show();
// ...
return next(adjustedReq).pipe(
finalize(() => {
loaderService.hide();
}),
);
};
anchorInjecting a server token
Do you inject a token that is provided by SSR? Even though standalone has not reached ExpressEngine, but if you have the root AppModule
and the root AppComponent
non standalone, you can still use provideHttpClient
. To inject a server provided string (specifically, the serverUrl
), you can use inject
function like this:
// local interceptor with serverUrl provided by ngExpressEngine for SSR
export const LocalInterceptorFn: HttpInterceptorFn = (
req: HttpRequest<any>,
next: HttpHandlerFn
) => {
// ...
// cast to 'any' if you do not want to wrestle with Angular and Typescript
const serverUrl = inject(<any>'serverUrl', {optional: true});
}
Cast the injection token to any
if you do not wish to wrestle with Angular and TypeScript. The alternative is ugly:
<ProviderToken<unknown>><unknown>'serverUrl'
Also pass the optional: true
flag to replace the @Optional
decorator in the previous DI method.
Building locally for SSR, the serverUrl
was passed in as expected.
Thank you for diving in with me today, did you have to hold your breath for more than 4 minutes? Hope not.