Search code examples
angularcomponentsangular-servicesviewchild

Dynamically add Component in angular 2/4


How can I add component dynamically?

toolbar.component.ts:

@Component({
  selector: 'app-toolbar',
  template: '<button>Add Text component</button>'
})
export class ToolbarComponent {
   constructor() { }
}  

section.component.ts:

@Component({
   selector: 'div[app-type=section]',
   template: ''
})
export class SectionComponent {
   constructor() { }
}  

text.component.ts:

@Component({
   selector: 'app-text',
   template: '<p>This is dynamically component</p>'
})
export class TextComponent {
   constructor() { }
}  

view.component.ts:

@Component({
   selector: 'app-view',
   template: `<div class="container">
<app-toolbar></app-toolbar>
<div app-type="section" id="SECTION1" class="active"></div>
<div app-type="section" id="SECTION2"></div>
</div>`
})
export class SectionComponent {}

when I click to ToolBarComponent, I want to add TextComponent to SectionComponent which have "active" class.


Solution

  • Expose viewContainerRef on section.component.ts:

    @Component({
       selector: 'div[app-type=section]',
       template: ''
    })
    export class SectionComponent {
      @Input() active: boolean;
    
       constructor(public viewContainerRef: ViewContainerRef) { }
    } 
    

    Add an output to toolbar.component.ts:

    @Component({
      selector: 'app-toolbar',
      template: '<button (click)="addComponentClick.emit()">Add Text component</button>'
    })
    export class ToolbarComponent {
      @Output() addComponentClick = new EventEmitter();
       constructor() { }
    } 
    

    In view.component.ts create a ComponentFactory for TextComponents to add them dynamically to active SectionComponents:

    import { Component, AfterViewInit, ViewChildren, QueryList, ElementRef, ComponentFactoryResolver, ComponentFactory, OnInit } from '@angular/core';
    import { TextComponent } from './text.component';
    import { SectionComponent } from './section.component';
    
    @Component({
       selector: 'app-view',
       template: `<div class="container">
    <app-toolbar (addComponentClick)="onAddComponentClick()"></app-toolbar>
    <div app-type="section" id="SECTION1" [active]="true"></div>
    <div app-type="section" id="SECTION2"></div>
    </div>`
    })
    export class ViewComponent implements AfterViewInit, OnInit {
      @ViewChildren(SectionComponent) sections: QueryList<SectionComponent>;
      activeSections: SectionComponent[];
      textComponentFactory: ComponentFactory<TextComponent>;
    
      constructor(private componentFactoryResolver: ComponentFactoryResolver) {  }
    
      ngOnInit() {
        this.textComponentFactory = this.componentFactoryResolver.resolveComponentFactory(TextComponent);
      }
    
      ngAfterViewInit() {
        this.activeSections = this.sections.reduce((result, section, index) => {
          if(section.active) {
            result.push(section);
          }
          return result;
        }, []);
      }
    
       onAddComponentClick() {
        this.activeSections.forEach((section) => {
          section.viewContainerRef.createComponent(this.textComponentFactory);
        });
       }
    }
    

    StackBlitz example