Stefani Gerber Senior Software Engineer

Mehrere Instanzen einer Angular4-Applikation auf einer HTML-Seite

Sep 8, 2017 7:02:10 AM

Angular-Applikationen müssen nicht immer eigenständig laufen und komplexe Funktionalitäten abdecken. Gerade im Zusammenhang von WCMS-Systemen wie Adobe AEM oder Magnolia kann es Sinn machen, in einer WCMS-Komponente eine Angular-App auszuliefern. So eine Komponente kann mehrfach auf einer Seite eingebunden werden (z.B. in verschiedenen Konfigurationen). Angular kann aber nicht standardmässig mehrere App-Instanzen auf derselben HTML-Seite starten; es wird nur die erste (gemäss Reihenfolge im DOM) App-Instanz gestartet.

Bei der Suche nach einer Lösung für dieses Problem habe ich verschiedene Lösungsansätze gefunden, aber keinen, der für Angular4 funktioniert hat UND mit einer nicht im voraus bekannten Anzahl an App-Instanzen umgehen kann. Die schlussendliche Lösung sieht nun folgendermassen aus:

  • Eigenes bootstrapping erzwingen: in NgModule-Config “bootstrap” auskommentieren und “entryComponents”
  • Im Dom alle App-Tags suchen, mit einer ID versehen und in der Komponente den Selektor überschreiben
import { BrowserModule } from '@angular/platform-browser';
import { ApplicationRef, ComponentFactoryResolver, NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

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

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule
  ],
  providers: [],
  //bootstrap: [AppComponent] //forces ngDoBootstrap into action
  entryComponents: [ AppComponent ]
})
export class AppModule {
  constructor(
        private resolver : ComponentFactoryResolver
    ) {}

  ngDoBootstrap(appRef: ApplicationRef) {
    const factory = this.resolver.resolveComponentFactory(AppComponent);
    const elements = document.getElementsByTagName(factory.selector);
    if (elements.length === 1) {
      appRef.bootstrap(factory);
    }else {
      const originalSelector = factory.selector;
      for (let i = 0; i < elements.length; i++) {
        elements[i].id = originalSelector + '_' + i;
        // access read-only property
        (factory).factory.selector = '#' + elements[i].id;
        appRef.bootstrap(factory);
      }
      (factory).factory.selector = originalSelector;
    }
  }
}

Unschön an der Lösung finde ich, das der Selektor nur per Hack überschrieben werden kann, da das “selector”-Property eigentlich read-only ist. Ich hoffe noch auf eine saubere Lösung, evtl. mit einer zukünftigen Angular-Version?