Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Angular 2 components throughout a non-Angular app

I have a fork of a preexisting, mature, non-Angular web app. I also have some existing Angular 2 components from a separate app that I wish to reuse. I would like to sprinkle the existing Angular components into the non-Angular app in various places. I would like to know if this is possible and advisable. Note that this is similar but not identical to Sprinkling Angular 2 components inside a non-angular page . I have a more specific scenario that I describe below.

Currently, I have figured out how to add single instances of these Angular components by bootstrapping a module that wraps my desired component. E.g. if I want to insert AppComponent into my existing non-Angular app:

In app.module.ts:

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

In app.component.ts:

@Component({
  selector: 'my-app'
  template: '<span>{{value}}</span>'
  ...
})
export class AppComponent { 
   public value: string;
   ...
 }

Whenever I want an AppComponent, I simply append a my-app element and bootstrap the module as follows:

platformBrowserDynamic().bootstrapModule(AppModule);

This works perfectly fine and gives the exact results I want for a single instance of AppComponent. However, my use cases require that I have two simultaneous instances of AppComponent in separate places of the DOM. If I attempt to bootstrap AppModule a second time, the core Angular code attaches the second AppModule to the my-app element that appears first in the DOM, effectively erasing my first instance of AppModule. Is there a way to tell Angular which my-app element to append to? Or is there any way around this?

HTML Example:

Let's say I append a <my-app> to <body>. Then my HTML looks like:

<body>
  <my-app>
  </my-app>
</body>

Next, if I call platformBrowserDynamic().bootstrapModule(AppModule); and set value = '111' anywhere in the AppComponent, my HTML looks like:

<body>
  <my-app>
    <span>111</span>
  </my-app>
</body>

Then I add another my-app selector to the HTML, so it looks like:

<body>
  <my-app>
    <span>111</span>
  </my-app>
  <my-app>
  </my-app>
</body>

Then, if I call platformBrowserDynamic().bootstrapModule(AppModule); and set value = '222' in the newly created AppComponenet anywhere in the AppComponent logic my HTML looks like:

<body>
  <my-app>
    <span>222</span>
  </my-app>
  <my-app>
  </my-app>
</body>

when I would like the desired result to be:

<body>
  <my-app>
    <span>111</span>
  </my-app>
  <my-app>
    <span>222</span>
  </my-app>
</body>

In summary: Is it possible and advisable to use Angular components throughout a non-Angular app? Is it possible to have 2 visible instances of the same Angular Module simultaneously?

Thanks!

like image 884
Connor Avatar asked Apr 11 '17 20:04

Connor


1 Answers

Following your scenario i would write the following code:

let bootstrapComponentFn: (node: HTMLElement) => void;

platformBrowserDynamic().bootstrapModule(AppModule).then((moduleRef: 

  NgModuleRef<AppModule) => {
    const appRef = moduleRef.injector.get(ApplicationRef);
    const zone: NgZone = moduleRef.injector.get(NgZone);
    const rootComponentFactory = (moduleRef as any).bootstrapFactories[0];  

    bootstrapComponentFn = (node) => {
      zone.run(() => {
        const compRef = rootComponentFactory.create(Injector.NULL, [], node);
       appRef.attachView(compRef.hostView);
      })
    }; 
  });
}

Some time later

setTimeout(function() {
  let node = document.createElement('my-app');
  document.body.appendChild(node);

  bootstrapComponentFn(node);
}, 2000);

See also Plunker Example

like image 71
yurzui Avatar answered Nov 15 '22 07:11

yurzui