Angular Platforms in depth. Part 2. Application bootstrap process
In this series of articles, I’m going to reveal to you how does it even possible — execute Angular applications across different environments. Also, we’ll learn how to build custom Angular platform which renders applications inside the system’s terminal using ASCII graphics.

The Angular framework was designed with flexibility in mind. That approach allows Angular applications to be executed across different environments — browser, server, web-worker, and even mobile devices are possible.
Articles:
- Angular Platforms in depth. Part 1. What are Angular Platforms?
- Angular Platforms in depth. Part 2. Application bootstrap process
- Angular Platforms in depth. Part 3. Rendering Angular applications in Terminal
Each Angular application starts with the main.ts
file:
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { PlatformRef } from '@angular/core';
// Create Browser Platform
const platformRef: PlatformRef = platformBrowserDynamic();
// Bootstrap Application
platformRef.bootstrapModule(AppModule);
Here, you could notice that we’re creating a new instance of PlatformRef
and then calling bootstrapModule
method on it. It’s a place where Angular application starts. In this article, we’re going to dive deeper into the application bootstrap process.
If you want to learn more on what are Angular platforms and how are they created, please, check previous article from the series: Angular Platforms in depth. Part 1. What are Angular Platforms?
As I stated previously, each Angular application starts with the following call:
platformRef.bootstrapModule(AppModule);
Here is the full code of bootstrapModule
method:
bootstrapModule<M>(
moduleType: Type<M>,
compilerOptions: (CompilerOptions & BootstrapOptions) | Array<CompilerOptions & BootstrapOptions> = [],
): Promise<NgModuleRef<M>> {
const options = optionsReducer({}, compilerOptions);
return compileNgModuleFactory(this.injector, options, moduleType)
.then(moduleFactory => {
const ngZoneOption = options ? options.ngZone : undefined;
const ngZone = getNgZone(ngZoneOption);
const providers: StaticProvider[] = [{ provide: NgZone, useValue: ngZone }];
return ngZone.run(() => {
const ngZoneInjector = Injector.create(
{ providers: providers, parent: this.injector, name: moduleFactory.moduleType.name });
const moduleRef = <InternalNgModuleRef<M>>moduleFactory.create(ngZoneInjector);
const exceptionHandler: ErrorHandler = moduleRef.injector.get(ErrorHandler, null);
if (!exceptionHandler) {
throw new Error('No ErrorHandler. Is platform module (BrowserModule) included?');
}
const localeId = moduleRef.injector.get(LOCALE_ID, DEFAULT_LOCALE_ID);
setLocaleId(localeId);
moduleRef.onDestroy(() => remove(this._modules, moduleRef));
ngZone !.runOutsideAngular(
() => ngZone !.onError.subscribe(
{
next: (error: any) => {
exceptionHandler.handleError(error);
},
}));
return _callAndReportToErrorHandler(exceptionHandler, ngZone !, () => {
const initStatus: ApplicationInitStatus = moduleRef.injector.get(ApplicationInitStatus);
initStatus.runInitializers();
return initStatus.donePromise.then(() => {
this._moduleDoBootstrap(moduleRef);
return moduleRef;
});
});
});
});
}
Let’s discuss it step by step.
Table of contents
- Module Compilation
- Root NgZone
- Error handling
- Initializers
- Bootstrap components
Module Compilation
The first step in the application bootstrap process is a module compilation.
bootstrapModule<M>(moduleType: Type<M>, options: CompilerOptions): Promise<NgModuleRef<M>> {
return compileNgModuleFactory(this.injector, options, moduleType)
.then((moduleFactory: NgModuleFactory) => {
// ...
});
}
So, when we’re calling bootstrapModule(AppModule, options)
on PlatformRef
first of all, it compiles that module. Here, moduleType
refers to the AppModule
. The injector is just an instance of Injector
injected through constructor. And options are compiler options provided as the second argument into bootstrapModule
method.
Let’s dive deeper at the compileNgModuleFactory
function to learn more about the module compilation process.
function compileNgModuleFactory<M>(
injector: Injector,
options: CompilerOptions,
moduleType: Type<M>
): Promise<NgModuleFactory<M>> {
const compilerFactory: CompilerFactory = injector.get(CompilerFactory);
const compiler = compilerFactory.createCompiler([options]);
return compiler.compileModuleAsync(moduleType);
}
First of all, Angular retrieves an instance of CompilerFactory
from the injector. CompilerFactory
is an abstract class which is responsible for the creation of an instance of the Compiler
. For instance, when we’re starting Angular application in dev mode, then JitCompilerFactory
implementation will be provided. Then, JitCompiler
implementation of the Compiler
will be created as a result of compilerFactory.createCompiler()
function call. Then, compiler
is asked to compiler our AppModule
.
export class JitCompiler {
private compileModuleAsync(moduleType: Type): Promise<NgModuleFactory> {
return this._loadModules(moduleType)
.then(() => {
this._compileComponents(moduleType);
return this._compileModule(moduleType);
});
}
}
Here, Angular loads all the modules, directives and pipes metadata. Then, it compiles all the components. During the compilation of the components, it searches for all components metadata registered in the application, then asks the compiler to compile all component’s templates in place. The last thing we need to do here is to actually compile the root application module. On that stage, Angular resolves all the required metadata for the module and return module factory.
When module compilation is done, PlatformRef
has moduleFactory and can start the bootstrap process.
Root NgZone
Before actually bootstrapping Angular application PlatformRef
need to create a root NgZone
.
const ngZone = new NgZone();
ngZone.run(() => {
const moduleRef = moduleFactory.create(this.injector);
// The rest bootstrap logic
});
Root NgZone
has to be instantiated even before AppModule
creation, because we need to wrap all the application logic inside the zone. Meanwhile, during creation Angular modules may create some providers eagerly, that’s why even root module creation logic has to be wrapped into the zone.
And only when the root NgZone
created PlatformRef
could instantiate the root module through the root module factory created as a result of the module compilation step.
Error handling
When root NgZone
created and root module already instantiated it’s time to set up a global error handler:
// Get error handler from injector
const exceptionHandler: ErrorHandler = injector.get(ErrorHandler);
// Setup error handling outside Angular
// To make sure change-detection will not be triggered
zone.runOutsideAngular(
// Subscribe on zone errors
() => zone.onError.subscribe({
next: (error: any) => {
// Call error handler
exceptionHandler.handleError(error);
}
})
);
ErrorHandler
in Angular, is responsible for proper errors logging and handling. So, to set up ErrorHandlerPlatformRef
need to retrieve provided ErrorHandler
from the injector. Then, subscribe to errors stream from the root zone and call handlerError
method as a reaction for each error event.
But look, all the error handling logic is wrapped in zone.runOutsideAngular
function. That function makes sure that any code executed inside will never trigger a change detection run.
Initializers
When ErrorHandler
set up, it’s a time to run application initializers.
const initStatus: ApplicationInitStatus = moduleRef.injector.get(ApplicationInitStatus);
initStatus.runInitializers().then(() => {
// ...
});
Here, Angular uses ApplicationInitStatus
entity to run application initializers. Application initializers are just functions, which execution required to be done right before the application bootstrap. For instance, the web worker platform has the following initializer:
{provide: APP_INITIALIZER, useValue: setupWebWorker, multi: true}
So, application initializers are just functions that provided under APP_INITIALIZER
token. Then, all APP_INITIALIZER
tokens injected into ApplicationInitStatus
using the following statement:
constructor(@Inject(APP_INITIALIZER) private appInits: (() => any)[]) {
When runInitializers
method called it just execute all the app initializers and return the result using Promise.all()
.
Bootstrap components
Ok, on that stage PlatformRef
is done with all preparations and ready to actually bootstrap AppComponent
! As you remember, above we’ve seen how to root module instance was created:
const moduleRef = moduleFactory.create(this.injector);
Each root Angular module has to contain bootstrap components array:
@NgModule({
bootstrap: [AppComponent],
})
export class AppModule {}
PlatformRef
then just iterates through that bootstrap components array and asks ApplicationRef
to actually bootstrap each component:
const appRef = injector.get(ApplicationRef);
moduleRef._bootstrapComponents.forEach(f => appRef.bootstrap(f));
ApplicationRef
inside just creates and renders components:
const componentFactory =
this._componentFactoryResolver.resolveComponentFactory(component);
const compRef = componentFactory.create();
The code above has to be familiar for those of you who created some Angular components dynamically. Here, we could notice how ComponentFactoryResolver
utilized to resolve componentFactory
for AppComponent
and then just creates it.
And that’s it! We did it! On that stage, we have AppComponent
rendered on the screen, which will render all the rest parts of the application.
Conclusion
Congratulations ? you’ve reached the end of the article. During the article, we went through the application bootstrap process. And now we have all the required knowledge to start building our custom platform which will render Angular applications inside the system’s terminal using ASCII graphics.
If you want to get deeper knowledge on Angular platforms, take a look at the rest articles of the series:
- Angular Platforms in depth. Part 1. What are Angular Platforms?
- Angular Platforms in depth. Part 2. Application bootstrap process
- Angular Platforms in depth. Part 3. Rendering Angular applications in Terminal
Also, follow me on twitter to be notified about new Angular articles as soon as possible!