3

I would like to bootstrap an Angular 4 micro app multiple times on the same page. Basically, foreach instance of class ‘.angular-micro-app’, bootstrap a new app instance.

I understand that traditionally these would be Angular components within a single parent App. In my case, that is not possible and I need multiple instances of the same root level app (component), on the same page. This was fairly trivial with AngularJS 1.x, but is proving rather frustrating with Angular.

Example:

index.html snippet:

<body>
<div class=“.angular-micro-app”></div>
…
<div class=“.angular-micro-app”></div> <!-- this element is NOT bootstrapping -->
</body>

app.component.ts:

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

@Component({
  selector: '.angular-micro-app',
  template: require('./app.component.html'),
})
export class AppComponent { }

app.module.ts:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';

import { AppComponent } from './app.component';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
  ],
  providers: [

  ],
  bootstrap: [AppComponent],
})
export class AppModule { }

In a main file, I’m doing the basic platform bootstrap:

platformBrowserDynamic().bootstrapModule(AppModule);

1 Answer 1

1

This can be accomplished by manually bootstrapping your root level component(s) in the NgModule ngDoBootstrap method.

(Note that in Angular 5+ this method may no longer be required, see this Angular PR)

We first find all root elements we want to bootstrap and give them a unique ID. Then for each instance, hack the component factory selector with the new ID and trigger the bootstrap.

const entryComponents = [
  RootComponent,
];

@NgModule({
  entryComponents,
  imports: [
    BrowserModule,
  ],
  declarations: [
    RootComponent,
  ],
})
export class MyModule {
  constructor(private resolver: ComponentFactoryResolver) {}

  ngDoBootstrap(appRef: ApplicationRef) {
    entryComponents.forEach((component: any) => {
      const factory = this.resolver.resolveComponentFactory(component);
      let selectorName;
      let elements;

      // if selector is a class
      if (factory.selector.startsWith('.')) {
        selectorName = factory.selector.replace(/^\./, '');
        elements = document.getElementsByClassName(selectorName);

      // else assume selector is an element
      } else {
        selectorName = factory.selector;
        elements = document.getElementsByTagName(selectorName);
      }

      // no elements found, early return
      if (elements.length === 0) {
        return;
      }

      // more than one root level componenet found, bootstrap unique instances
      if (elements.length > 1) {
        const originalSelector = factory.selector;

        for (let i = 0; i < elements.length; i += 1) {
          elements[i].id = selectorName + '_' + i;
          (<any>factory).factory.selector = '#' + elements[i].id;
          appRef.bootstrap(factory);
        }

        (<any>factory).factory.selector = originalSelector;

      // only a single root level component found, bootstrap as usual
      } else {
        appRef.bootstrap(factory);
      }
    });
  }
}

Now, assuming our RootComponent's selector was '.angular-micro-app' this will work as expected:

<body>
    <div class="angular-micro-app"></div>
    ...
    <div class="angular-micro-app"></div>
</body>
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.