3

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.

bui quang huy
  • 153
  • 1
  • 2
  • 12
  • 2
    Create a service with Subject(or BehaviourSubject) in it, in ToolbarComponent make Observable from click event, subscribe and `next` to that Subject from service. In that place where class `active` is added, `next` to other Subject in service (just now value of `next` is a reference to that element/component), so you wont need to query DOM. In ViewComponent dynamicly create component with `componentFactoryResolver`. Subscribe to both subjects and append component via `ng-template` and directive added to it. You probably can create `ng-template somedirectiveRef` dynamicly, and not in every HTML – Julius Dzidzevičius Mar 03 '18 at 09:42
  • Can you help me with a full answer? Plz. Actually class "active" is not necessary, exactly I want to add the component to the section that is most visible on the screen. :D - like this question: https://stackoverflow.com/questions/38360676/get-the-element-which-is-the-most-visible-on-the-screen – bui quang huy Mar 03 '18 at 10:58
  • Please show your updated code then. If dynamic placeholder not needed - just hardocde in HTML - `
    `
    – Julius Dzidzevičius Mar 03 '18 at 15:14

3 Answers3

10

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

jeffergj13
  • 141
  • 8
  • How I will access all TextComponent "__id" variables?https://stackblitz.com/edit/so-dynamic-component-to-child-injywm?file=app%2Ftext.component.ts Thanks in advance – Libu Mathew Oct 12 '18 at 12:12
2

I would do it using ngFor view.component.ts:

@Component({
   selector: 'app-view',
   template: `
     <div class="container">
       <app-toolbar (addEvent)="addEvent($event)"></app-toolbar>
       <div app-type="section" id="SECTION1" class="active">
          <app-text *ngFor="let appText in textArray"></app-text>
       </div>
       <div app-type="section" id="SECTION2"></div>
     </div>
   `
})
export class SectionComponent {
   public textArray: string[] = [];
   public addEvent(event: string) : void {
      textArray.push(event);
   }
   ....
}

toolbar.component.ts:

import { Component, EventEmitter, Output } from '@angular/core';
@Component({
  selector: 'app-toolbar',
  template: '<button (click)="addNewText()">Add Text component</button>'
})
export class ToolbarComponent {
   @Output addEvent: EventEmitter<string> = new EventEmitter();
   constructor() { }
   addNewText(): void {
      this.addEvent.emit("");
   }
   ....
}  
ambussh
  • 740
  • 1
  • 7
  • 18
  • Thank for help, but I want to use component. "TextComponent" is just my example. I have many of the same components. Ex: "HeadingH1Component", "ListULComponent", "ImageImgComponent"... – bui quang huy Mar 03 '18 at 11:03
2

I have another way of doing it, please check for you case

html
<ng-container *ngComponentOutlet="COMPONENT"></ng-container>

.ts
import { COMPONENT } from './..dir../component'

OR

html
<ng-container *ngComponentOutlet="option.component"></ng-container>

.ts
import { COMPONENT } from './..dir../component'    
option = {
  component: COMPONENT
}

OR if it is Dynamic, add the component to entryComponent

app.module.ts
import { COMPONENT } from './..dir../component'
@NgModule({
   declarations: [COMPONENT],
   /.
   ..
   ..
  ./
  entryComponents: [COMPONENT]
});    

html
<ng-container *ngComponentOutlet="option.component"></ng-container>

.ts
/** The COMPONENT is not imported since it is added to entrycomponent**/
@Input() option;
Rishi0405
  • 354
  • 1
  • 11