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.

3
  • 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 Commented Mar 3, 2018 at 9: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: stackoverflow.com/questions/38360676/… Commented Mar 3, 2018 at 10:58
  • Please show your updated code then. If dynamic placeholder not needed - just hardocde in HTML - <div app-type="section" id="SECTION1" class="active"> <ng-template myDirectiveThatReferencesTemplateComponentForInsertion></ng-template></div> Commented Mar 3, 2018 at 15:14

3 Answers 3

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

Sign up to request clarification or add additional context in comments.

1 Comment

How I will access all TextComponent "__id" variables?stackblitz.com/edit/… Thanks in advance
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("");
   }
   ....
}  

1 Comment

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"...
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;

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.