- Published on
How and When to Use App Initializer in a Standalone Angular 16 Application
- Authors
- Name
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_INITIALIZER
s 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! :-)