Search code examples
angularangular-httpclientangular-providersangular-dependency-injection

How to make an HTTP call from useFactory function during provider configuration in Angular?


I have the next provider configuration

  {
          provide: APP_INITIALIZER,
          useFactory: initializeKeycloak,
          multi: true,
          deps: [KeycloakService],
        },

How to make an HTTP call from useFactory function (initializeKeycloak)?

function initializeKeycloak(keycloak: KeycloakService) {

  return () =>
    keycloak.init({
      config: {
        url: environment.keycloak.url,
        realm: environment.keycloak.realm,
        clientId: environment.keycloak.clientId
      }
    });
}

I want to get this value from the assets folder

environment.keycloak.url

Solution

  • There are 2 things you need to know here:

    • In your asyncFactory function you can return a promise that indicates when the intialization is done.
    • You can add the HttpClient to the dependencies of the APP_INITIALIZER. Note that you must add the HttpClientModule to the imports of your AppModule so that it is available for injection.

    Working example for RxJS 6:

    const initializeKeycloak = (
      keycloakService: KeycloakService,
      httpClient: HttpClient // The httpClient is available here because you added it to the deps of the APP_INITIALIZER
    ) => {
      return () =>
        httpClient
          .get(url)
          .pipe(
            tap((environment: any) => {
              keycloakService.init({
                config: {
                  url: environment.keycloak.url,
                  realm: environment.keycloak.realm,
                  clientId: environment.keycloak.clientId
                }
              });
            })
          )
          .toPromise(); // Return the promise to indicate the initialization is done.
    };
    
    @NgModule({
      imports: [BrowserModule, FormsModule, HttpClientModule],
      declarations: [AppComponent, HelloComponent],
      bootstrap: [AppComponent],
      providers: [
        {
          provide: APP_INITIALIZER,
          useFactory: initializeKeycloak,
          multi: true,
          deps: [KeycloakService, HttpClient] // Add the HttpClient here.
        },
        KeycloakService
      ]
    })
    export class AppModule {}
    
    

    Working example for RxJS 7+ (toPromise will become deprecated, you have to use lastValueFrom):

    import { lastValueFrom } from "rxjs";
    ...
    
    const initializeKeycloak = (
      keycloakService: KeycloakService,
      httpClient: HttpClient
    ) => {
      return () => {
        const request$ = httpClient.get(url).pipe(
          tap((environment: any) => {
            keycloakService.init({
              config: {
                url: environment.keycloak.url,
                realm: environment.keycloak.realm,
                clientId: environment.keycloak.clientId
              }
            });
          })
        );
        return lastValueFrom(request$);
      };
    };
    

    On a side note:

    While it is possible to retrieve the environment information on startup, this has the drawback that the app won't load at all if the request fails. It might be worth it here to add a retry mechanism.

    There are also ways to inject environment variables at build-time, I provided information on that topic in this answer.