Logo
Published on

How and When to Use App Initializer in a Standalone Angular 16 Application

Authors
  • Name
    Twitter

How to make sure we have all the data we need on application start?

With more complex applications, there might be a need to make sure some initial data is already available at startup, directly when your application has been initialized like:

  • user specific data
  • translations
  • some external configurations
  • redirections
  • authentication.

These use-cases are most popular motivation to start looking for some better solution than ngOnInit lifecycle hook, as the moment when OnInit is being executed might have been already too late.

Maybe you have to provide some content within specific language or need to handle some authentication methods (like JWT tokens). You cannot just wait until your application has been already bootstrapped and then start executing these scenarios.

Angular teams has already provided us a Dependency Injection Token called APP_INITIALIZER . Provided functions are being injected on application startup and executed accordingly during app initialization process. Which basically means, that our application will start when all our APP_INITIALIZERs have been resolved or completed.

And yes, you can execute asynchronous operations as well :) It doesn’t really matter if the factory function returns Promise or Observable . Initialization process will not complete until these are being handled (including redirections with Router).

I’ve intentionally used plural, you can provide as many APP_INITIALIZER s tokens as you need (Each APP_INITIALIZER can handle entirely different topic / use-case / scenario).

You just need to make sure to provide each within the providers array of either your AppModule or main.ts file (in case of standalone components).

import { enableProdMode, isDevMode } from "@angular/core";
import { bootstrapApplication } from "@angular/platform-browser";

import { environment } from "./environments/environment";
import { AppComponent } from "./app/app.component";
import { rootRoutes } from "./app/root-routes";
import { provideRouter, Router } from "@angular/router";
import { HttpClient, provideHttpClient } from "@angular/common/http";
import { from, mergeMap, Observable } from "rxjs";
import { User } from "./app/...";

if (environment.production) {
  enableProdMode();
}

export function initializeUserData(http: HttpClient, router: Router) {
  return (): Observable<void> =>
    http
      .get("https://api.some-endpoint.com/user")
      .pipe(
        mergeMap(({ type }: User) =>
          from(
            router.navigate([type === "CORPORATE" ? "corporate" : "private"])
          )
        )
      );
}

bootstrapApplication(AppComponent, {
  providers: [
    provideHttpClient(),
    provideRouter(rootRoutes),
    {
      provide: APP_INITIALIZER,
      useFactory: initializeUserData,
      multi: true,
      deps: [HttpClient, Router],
    },
  ],
}).catch((err) => console.error(err));

Let’s analyze the code snippet from main.ts file from our application. We’ve declared initializeUserData where we use HttpClient and Router to make sure, our user is being navigated based on his/hers preferences. We wait for the data to be loaded and then for a redirection to finish.

Within providers array, we declared our APP_INITIALIZER and passed our initializeUserData function to useFactory attribute. As we’re using some of the dependencies within our factory function, we have to declare deps array and pass all dependencies needed. Although, in case we do not need any dependencies, deps array can be ignored.

This is just a basic example, you might want to keep fetched user data within some data state management system, like ngrx and you’re fine to combine this within your initializer as well. Just make sure you provide your Store Provider before APP_INITIALIZER . Just remember, ordering is important here!

That was easy, wasn’t it?

Need consultancy on your project? Do not screw up your app and get in touch! :-)