Sekrab Garage

Angular Standalone Feature

Angular 15 standalone HTTPClient provider: Another update

Angular November 28, 22
Subscribe to Sekrab Parts newsletter.
Series:

Standalone

In this article, we will turn some components in an existing Angular app into standalone components, to see if this feature is worth the trouble. What else do they need to work properly? What scenarios are they best used for? How do they affect development experience? and Output bundles?
  1. two years ago
    How to turn an Angular app into standalone - Part I
  2. two years ago
    How to turn an Angular app into standalone - Part II
  3. two years ago
    Angular standalone router providers: an update
  4. two years ago
    Angular 15 standalone HTTPClient provider: Another update
  5. 12 months ago
    Angular Standalone in SSR: update

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.

  1. two years ago
    How to turn an Angular app into standalone - Part I
  2. two years ago
    How to turn an Angular app into standalone - Part II
  3. two years ago
    Angular standalone router providers: an update
  4. two years ago
    Angular 15 standalone HTTPClient provider: Another update
  5. 12 months ago
    Angular Standalone in SSR: update