Bootstrap

How to manually bootstrap an Angular application

Official Angular documentation states that to bootstrap an application you have to put the following in the main.ts file: The first part of the...

Written by Luci · 3 min read >

Official Angular documentation states that to bootstrap an application you have to put the following in the main.ts file:

platformBrowserDynamic().bootstrapModule(AppModule);

The first part of the statement platformBrowserDynamic() creates a platform. Angular docs describe the platform as:

the entry point for Angular on a web page. Each page has exactly one platform, and services (such as reflection) which are common to every Angular application running on the page are bound in its scope.

Angular also has a concept of the running application instance that you can usually inject using ApplicationRef token. There potentially can be many applications on one platform. Each application is created from the module using bootstrapModule method. This is exactly the method that is used in main.ts. So the statement shown in the docs first creates a platform and then the application instance.

When the application is being created Angular checks the bootstrap property of the module used to bootstrap the application (AppModule):

@NgModule({
  imports: [BrowserModule],
  declarations: [AppComponent],
  bootstrap: [AppComponent]
})
export class AppModule {}

This property usually references the component you want to bootstrap the application with. Then Angular finds the element that is the selector of bootstrapped component in the DOM and initializes the component.

The above process implies that you know what component you want to bootstrap the application with. But imagine a situation when the component to bootstrap the application is defined by the server during runtime. How can you bootstrap the application later when you get this information? As it turns out it’s a pretty straightforward process.

NgDoBootstrap

Imagine we have two components A component and B component. We will decide during runtime which one should be used in the application. Let’s define these two components:

import { Component } from '@angular/core';

@Component({
  selector: 'a-comp',
  template: `<span>I am A component</span>`
})
export class AComponent {}

@Component({
  selector: 'b-comp',
  template: `<span>I am B component</span>`
})
export class BComponent {}

And we register them in the AppModule:

@NgModule({
  imports: [BrowserModule],
  declarations: [AComponent, BComponent],
  entryComponents: [AComponent, BComponent]
})
export class AppModule {}

The important thing here is that we don’t register them in the bootstrap property since we will be bootstrapping them manually. Also we should register them in the entryComponents since we want the compiler to create factories for them. Angular automatically adds all components specified in the bootstrap property to entry components that is why you usually don’t add the root component to the entryComponents.

Also, since we don’t know whether A or B component will be used we don’t specify there selectors in the index.html, so it now it looks like this:

<body>
  <h1 id="status">
     Loading AppComponent content here ...
  </h1>
</body>

Now, if you run the application now you will get the following error:

The module AppModule was bootstrapped, but it does not declare “@NgModule.bootstrap” components nor a “ngDoBootstrap” method. Please define one of these

So basically Angular complains that we didn’t specify what component should be used for bootstrapping. And we don’t know beforehand. We will be bootstrapping the app manually later and to do that we need to add ngDoBoostrap method to the AppModule class:

export class AppModule {
  ngDoBootstrap(app) {  }
}

Angular passes the reference to the running application in the form of ApplicationRef to this method. Later, when we will be ready to bootstrap the application we will use bootstrap method of the ApplicationRef to initialize the root component.

Let’s define the custom method bootstrapRootComponent that will be responsible for bootstrapping the root component when it becomes available:

// app - reference to the running application (ApplicationRef)
// name - name (selector) of the component to bootstrap
function bootstrapRootComponent(app, name) {
  // define the possible bootstrap components 
  // with their selectors (html host elements)
  const options = {
    'a-comp': AComponent,
    'b-comp': BComponent
  };
  // obtain reference to the DOM element that shows status
  // and change the status to `Loaded`
  const statusElement = document.querySelector('#status');
  statusElement.textContent = 'Loaded';
  // create DOM element for the component being bootstrapped
  // and add it to the DOM
  const componentElement = document.createElement(name);
  document.body.appendChild(componentElement);
  // bootstrap the application with the selected component
  const component = options[name];
  app.bootstrap(component);
}

It takes the reference to the ApplicationRef and the name of the component to bootstrap. Also, we defined the options map with all possible bootstrap components. The component selector acts as a key. We will use it to lookup a component class when we get the required information from the server.

I’ve also created a mock fetch function that emulates HTTP request to the server and returns b-comp selector within 2 seconds:

function fetch(url) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('b-comp');
    }, 2000);
  });
}

So now that we have our bootstrap function that will bootstrap the root component let’s use it in the ngDoBootstrap method of the module:

export class AppModule {
  ngDoBootstrap(app) {
    fetch('url/to/fetch/component/name')
      .then((name)=>{ this.bootstrapRootComponent(app, name)});
  }
}

And that’s it. Here is the stackblitz example that demonstrates the solution.

Does it work with AOT?

Yes, sure it does. You just have to precompile all your components and use the factories when bootstrapping the application:

import {AComponentNgFactory, BComponentNgFactory} from './components.ngfactory.ts';
@NgModule({
  imports: [BrowserModule],
  declarations: [AComponent, BComponent]
})
export class AppModule {
  ngDoBootstrap(app) {
    fetch('url/to/fetch/component/name')
      .then((name)=>{ this.bootstrapRootComponent(app, name)});
  }
  bootstrapRootComponent(app, name) {
    const options = {
      'a-comp': AComponentNgFactory,
      'b-comp': BComponentNgFactory
    };

Note that we don’t need to specify components in the entryComponents since we already have the factories and don’t need to compile them.

Written by Luci
I am a multidisciplinary designer and developer with a main focus on Digital Design and Branding, located in Cluj Napoca, Romania. Profile
0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x