APP_BOOTSTRAP_LISTENER
Angular exposes a way to attach callbacks to the bootstrap sequence. The snippet below shows where those callbacks are triggered in the framework source:
private _loadComponent(componentRef: ComponentRef<any>): void {
this.attachView(componentRef.hostView);
this.tick();
this._rootComponents.push(componentRef);
// Get the listeners lazily to prevent DI cycles.
const listeners =
this._injector.get(APP_BOOTSTRAP_LISTENER, []).concat(this._bootstrapListeners);
listeners.forEach((listener) => listener(componentRef));
}
When Angular initializes the application, it invokes this function. Not only does it reveal how components get attached to the app, it also demonstrates that for every component being bootstrapped, Angular fires the listeners registered under the APP_BOOTSTRAP_LISTENER token, handing each listener the just-initialized component.
In practice, that means you can tap into the bootstrap lifecycle to run your own setup logic. The Router uses this exact mechanism to register itself for bootstrap and then runs some of its own initialization.
Because Angular hands the fully-initialized component to your callback, you can easily reach the root ComponentRef of the whole application:
import {APP_BOOTSTRAP_LISTENER, ...} from '@angular/core';
@NgModule({
imports: [BrowserModule, ReactiveFormsModule, TasksModule],
declarations: [AppComponent, BComponent, AComponent, SComponent, LiteralsComponent],
providers: [{
provide: APP_BOOTSTRAP_LISTENER, multi: true, useFactory: () => {
return (component: ComponentRef<any>) => {
console.log(component.instance.title);
}
}
}],
bootstrap: [AppComponent]
})
export class AppModule {}
While digging through the sources I noticed this feature, then checked the docs and found it marked as experimental. The official description reads:
All callbacks provided via this token will be called for every component that is bootstrapped. Signature of the callback:
(componentRef: ComponentRef) => void
APP_INITIALIZER
Angular also gives you a chance to complete asynchronous or setup work before it marks the application as fully initialized and starts running change detection and rendering templates. The actual initialization happens here:
constructor(@Inject(APP_INITIALIZER) @Optional() appInits: (() => any)[]) {
const asyncInitPromises: Promise<any>[] = [];
if (appInits) {
for (let i = 0; i < appInits.length; i++) {
const initResult = appInits[i]();
if (isPromise(initResult)) {
asyncInitPromises.push(initResult);
}
}
}
Following the same pattern as with APP_BOOTSTRAP_LISTENER, you simply provide a function under the APP_INITIALIZER token and it gets called during setup. For instance, this example holds off Angular startup for five seconds:
{
provide: APP_INITIALIZER,
useFactory: () => {
return () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, 5000);
});
}
},
multi: true
}
You are also free to declare several initializers at once, like this:
{
provide: APP_INITIALIZER,
useFactory: () => {
return () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, 5000);
});
}
},
multi: true
},
{
provide: APP_INITIALIZER,
useFactory: () => {
return () => {
return new Promise.resolve(2);
}
},
multi: true
}
BootstrapModule
There is yet one more spot where custom logic can be injected into the bootstrap flow: the bootstrapModule method.
platform.bootstrapModule(AppModule).then((module) => {
let applicationRef = module.injector.get(ApplicationRef);
let rootComponentRef = applicationRef.components[0];
});
From there you receive the NgModuleRef of the bootstrapped module, which in turn lets you reach both the ApplicationRef and the ComponentRef.
