19

I'm looking for a way to instantiate a component in Angular2 from within the code of another component. Unlike the many people that have asked a similar question I'm not so much interested in dynamically compiling a new component, just instantiating and inserting one that already exists in my application.

For instance:

Say I have two components:

dashboard-item.component.ts

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

@Component({
    selector: "dashboard-item",
    template: "Some dashboard item with functionality"
})
export class DashboardItemComponent {
    constructor() {}

    onInit() {}
}

dashboard.component.ts

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

@Component({
    selector: "dashboard",
    template: "<h1>Dashboard!</h1><div #placeholder></div>"
})
export class DashboardComponent {
    constructor() {}

    onInit() {}
}

What I'm looking for is a way to create a DashboardItemComponent in the onInit of the DashboardComponent and add it to the #placeholder div.

Two things to note:

These two earlier issues ask a similar question, but their answers are either rather lackluster or pertain to earlier (beta) versions of Angular2 and no longer seem to work.

Community
  • 1
  • 1
Robba
  • 7,684
  • 12
  • 48
  • 76
  • You can take a look at the first option from http://stackoverflow.com/questions/39678963/load-existing-components-dynamically-angular-2-final-release/39680765#39680765 – yurzui Sep 25 '16 at 09:21

4 Answers4

20

Here's a working demo: https://plnkr.co/edit/pgkgYEwSwft3bLEW95Ta?p=preview

import {Component, NgModule, ViewChild, ElementRef, Input, Output, EventEmitter, ViewContainerRef, ComponentRef, ComponentFactoryResolver, ReflectiveInjector} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'

@Component({
  selector: 'any-comp',
  template: '<div (click)="clicked.emit($event)">here i am.. {{name}}</div>'
})
export class AnyComponent {

  @Input() name;
  @Output() clicked = new EventEmitter();

  constructor() {
    console.log('some1 created me.. ! :)');
  }
}

@Component({
  selector: 'my-app',
  template: `
    <div>
      <h2>Hello {{name}}</h2>
      <template #placeHolder>
      </template>
    </div>
  `,
})
export class App {

  @ViewChild('placeHolder', {read: ViewContainerRef}) private _placeHolder: ElementRef;

  name:string;
  constructor(private _cmpFctryRslvr: ComponentFactoryResolver) {
    this.name = 'Angular2'
  }

  ngOnInit() {
    let cmp = this.createComponent(this._placeHolder, AnyComponent);

    // set inputs..
    cmp.instance.name = 'peter';

    // set outputs..
    cmp.instance.clicked.subscribe(event => console.log(`clicked: ${event}`));

    // all inputs/outputs set? add it to the DOM ..
    this._placeHolder.insert(cmp.hostView);
  }

  public createComponent (vCref: ViewContainerRef, type: any): ComponentRef {

    let factory = this._cmpFctryRslvr.resolveComponentFactory(type);

    // vCref is needed cause of that injector..
    let injector = ReflectiveInjector.fromResolvedProviders([], vCref.parentInjector);

    // create component without adding it directly to the DOM
    let comp = factory.create(injector);

    return comp;
  }
}

@NgModule({
  imports: [ BrowserModule ],
  declarations: [ App, AnyComponent ], // ! IMPORTANT
  entryComponents: [ AnyComponent ], // ! IMPORTANT --> would be lost due to Treeshaking..
  bootstrap: [ App ]
})
export class AppModule {}
slaesh
  • 16,659
  • 6
  • 50
  • 52
  • 1
    Thanks for the input, this surely helps. You mention that the entryComponents is important because of treeshaking. While I don't really know what that means, without it it doesn't work. Is this treeshaking process always performed when Angular performs its compilation? – Robba Sep 25 '16 at 10:05
  • seems like, yes. it is never instantiated anywhere.. so it can be optimized and will be deleted.. – slaesh Sep 25 '16 at 10:11
  • Trying this today (Angular 13), `_cmpFctryRslvr` is unknown and `ReflectiveInjector` is deprecated. Looks like more relevant answers for today's world are below. – Brian White Mar 21 '23 at 23:06
6

In case if anyone wants to avoid any statement in accepted answer here is the snippet

    public createComponent<T>(vCref: ViewContainerRef, type: Type<T>): ComponentRef<T> {

      let factory = this._cmpFctryRslvr.resolveComponentFactory(type);

      // vCref is needed cause of that injector..
      let injector = ReflectiveInjector.fromResolvedProviders([], vCref.parentInjector);

      // create component without adding it directly to the DOM
      let comp = factory.create(injector);

      return comp;
    }
Xpleria
  • 5,472
  • 5
  • 52
  • 66
Puneeth Rai
  • 1,163
  • 10
  • 11
4

There seems to be a (new?) API function for doing what is described in mxii's answer. The ViewContainerRef has the createComponent method. It instantiates the component and adds it to the view.

let factory = this._cmpFctryRslvr.resolveComponentFactory(AnyComponent);
let cmp = this.viewContainer.createComponent(factory);
cmp.instance.name = 'peter';
PhillipM
  • 441
  • 4
  • 7
-4

You can place your child component inside parent component.

<parent-component>
  <child-component></child-component>
</parent-component>

The child component exposes an EventEmitter property with which it emits events when something happens. The parent binds to that event property and reacts to those events. https://angular.io/docs/ts/latest/cookbook/component-communication.html

Manish
  • 2,092
  • 16
  • 18