2

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
John Doe
  • 3,794
  • 9
  • 40
  • 72

1 Answers1

1

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.

code-gorilla
  • 2,231
  • 1
  • 6
  • 21
  • 1
    toPromise is being deprecated from rx.js 7. How can I implement this without toPromise operator? – John Doe Nov 21 '20 at 11:56
  • Did not know that yet! The problem is that lastValueFrom which would replace the usage of toPromise in this case is not yet available. So I added 2 solutions, one for 6 and one for 7+. You could also wrap the observable in a promise, but that is uglier in my opinion: `return new Promise((res, rej) => httpClient.get(url).pipe(...).subscribe(res, rej));` – code-gorilla Nov 21 '20 at 15:04