How to dynamically create bootstrap modals as Angular2 components?

update for 2.0.0 final

Plunker example >= 2.0.0

@NgModule({
  imports: [ BrowserModule ],
  declarations: [ App, ModalComponent, CompComponent],
  providers: [SharedService],
  entryComponents: [CompComponent],
  bootstrap: [ App, ModalComponent ]
})
export class AppModule{}
export class ModalComponent {
  @ViewChild('theBody', {read: ViewContainerRef}) theBody;

  cmp:ComponentRef;

  constructor(
    sharedService:SharedService, 
    private componentFactoryResolver: ComponentFactoryResolver, 
    injector: Injector) {

    sharedService.showModal.subscribe(type => {
      if(this.cmp) {
        this.cmp.destroy();
      }
      let factory = this.componentFactoryResolver.resolveComponentFactory(type);
      this.cmpRef = this.theBody.createComponent(factory)
      $('#theModal').modal('show');
    });
  }

  close() {
    if(this.cmp) {
      this.cmp.destroy();
    }
    this.cmp = null;
  }
}

Hint

If one application change the state in SharedService or calls a method that causes an Observable to emit a value and the subscriber is in a different application then the emitter, the code in the subscriber is executed in the NgZone of the emitter.

Therefore when subscribing to an observable in SharedService use

class MyComponent {
  constructor(private zone:NgZone, private sharedService:SharedService) {
    private sharedService.subscribe(data => this.zone.run() => {
      // event handler code here
    });
  }
}

For more details how to trigger change detection see Triggering Angular2 change detection manually

original

Dynamically added HTML is not processed by Angular and doesn’t result in components or directives to be instantiated or added.

You can’t add components outside Angulars root component (AppComponent) using DynamicComponentLoader (deprecated) ViewContainerRef.createComponent() (Angular 2 dynamic tabs with user-click chosen components).

I guess the best approach is to create a 2nd component outside Angulars root component is to call bootstrap() on each and use a shared service to communicate:

var sharedService = new SharedService();

bootstrap(AppComponent, [provide(SharedService, {useValue: sharedService})]);
bootstrap(ModalComponent, [provide(SharedService, {useValue: sharedService})]);

Plunker example beta.17
Plunker example beta.14

@Injectable()
export class SharedService {
  showModal:Subject = new Subject();
}

@Component({
  selector: 'comp-comp',
  template: `MyComponent`
})
export class CompComponent { }


@Component({
  selector: 'modal-comp',
  template: `
  <div class="modal fade" id="theModal" tabindex="-1" role="dialog" aria-labelledby="theModalLabel">
    <div class="modal-dialog largeWidth" role="document">
      <div class="modal-content">
        <div class="modal-header">
        <h4 class="modal-title" id="theModalLabel">The Label</h4></div>
        <div class="modal-body" #theBody>
      </div>
    <div class="modal-footer">
    <button type="button" class="btn btn-default" data-dismiss="modal" (close)="close()">Close</button>
  </div></div></div></div>
`
})
export class ModalComponent {
  cmp:ComponentRef;
  constructor(sharedService:SharedService, dcl: DynamicComponentLoader, injector: Injector, elementRef: ElementRef) {
    sharedService.showModal.subscribe(type => {
      if(this.cmp) {
        this.cmp.dispose();
      }
      dcl.loadIntoLocation(type, elementRef, 'theBody')
      .then(cmp => {
        this.cmp = cmp;
        $('#theModal').modal('show');
      });
    });
  }

  close() {
    if(this.cmp) {
      this.cmp.dispose();
    }
    this.cmp = null;
  }
}


@Component({
  selector: 'my-app',
  template: `
<h1>My First Attribute Directive</h1>
<button (click)="showDialog()">show modal</button>
<br>
<br>`,
})
export class AppComponent {
  constructor(private sharedService:SharedService) {}

  showDialog() {
    this.sharedService.showModal.next(CompComponent);
  }
}

Leave a Comment