77

The current official docs only shows how to dynamically change components within an <ng-template> tag. https://angular.io/guide/dynamic-component-loader

What I want to achieve is, let's say I have 3 components: header, section, and footer with the following selectors:

<app-header>
<app-section>
<app-footer>

And then there are 6 buttons that will add or remove each component: Add Header, Add Section, and Add Footer

and when I click Add Header, the page will add <app-header> to the page that renders it, so the page will contain:

<app-header>

And then if I click Add Section twice, the page will now contain:

<app-header>
<app-section>
<app-section>

And if I click Add Footer, the page will now contain all these components:

<app-header>
<app-section>
<app-section>
<app-footer>

Is it possible to achieve this in Angular? Note that ngFor is not the solution I'm looking for, as it only allows to add the same components, not different components to a page.

EDIT: ngIf and ngFor is not the solution I'm looking for as the templates are already predetermined. What I am looking for is something like a stack of components or an array of components where we can add, remove, and change any index of the array easily.

EDIT 2: To make it more clear, let's have another example of why ngFor does not work. Let's say we have the following components:

<app-header>
<app-introduction>
<app-camera>
<app-editor>
<app-footer>

Now here comes a new component, <app-description>, which the user wants to insert in between and <app-editor>. ngFor works only if there is one same component that I want to loop over and over. But for different components, ngFor fails here.

ngShravil.py
  • 4,742
  • 3
  • 18
  • 30
  • If they are known, as in your example, you could use *ngIf on the header and footer and *ngFor on the sections. – DeborahK Jul 06 '17 at 04:57
  • @DeborahK ngIf and ngFor is not the solution I'm looking for as the templates are already predetermined. What I am looking for is something like a stack of components or an array of components where we can add, remove, and change any index of the array easily. – Marcellino Corleone Jul 06 '17 at 06:38
  • This might also help if you want to add different kinds of components https://stackoverflow.com/questions/36325212/angular-2-dynamic-tabs-with-user-click-chosen-components/36325468#36325468 – Günter Zöchbauer Jul 06 '17 at 06:57
  • @GünterZöchbauer Thanks! This might be especially useful as I get to know ngComponentOutlet – Marcellino Corleone Jul 06 '17 at 08:10
  • `ngComponentOutlet` is still lacking some basic features. There is a pull request to address these since a long time but it wasn't yet merged AFAIK. So reading and passing values from/to dynamically added components doesn't work well. – Günter Zöchbauer Jul 06 '17 at 08:11
  • I know this is an old question, but if anyone comes to this in the future looking for information about how to dynamically add components, the first step is to be *really* sure you actually need dynamic components. If you have a (relatively) small list of components that might be nested in different ways, you should seriously consider creating a data structure that represents your nested components, and a series of components that can render each other as children, using a combination of `ngFor` and `ngSwitch`. – Coderer Nov 27 '20 at 15:24

2 Answers2

95

What you're trying to achieve can be done by creating components dynamically using the ComponentFactoryResolver and then injecting them into a ViewContainerRef. One way to do this dynamically is by passing the class of the component as an argument of your function that will create and inject the component.

See example below:

import {
  Component,
  ComponentFactoryResolver, Type,
  ViewChild,
  ViewContainerRef
} from '@angular/core';

// Example component (can be any component e.g. app-header app-section)
import { DraggableComponent } from './components/draggable/draggable.component';

@Component({
  selector: 'app-root',
  template: `
    <!-- Pass the component class as an argument to add and remove based on the component class -->
    <button (click)="addComponent(draggableComponentClass)">Add</button>
    <button (click)="removeComponent(draggableComponentClass)">Remove</button>

    <div>
      <!-- Use ng-template to ensure that the generated components end up in the right place -->
      <ng-template #container>

      </ng-template>
    </div>

  `
})
export class AppComponent {
  @ViewChild('container', {read: ViewContainerRef}) container: ViewContainerRef;

  // Keep track of list of generated components for removal purposes
  components = [];

  // Expose class so that it can be used in the template
  draggableComponentClass = DraggableComponent;

  constructor(private componentFactoryResolver: ComponentFactoryResolver) {
  }

  addComponent(componentClass: Type<any>) {
    // Create component dynamically inside the ng-template
    const componentFactory = this.componentFactoryResolver.resolveComponentFactory(componentClass);
    const component = this.container.createComponent(componentFactory);

    // Push the component so that we can keep track of which components are created
    this.components.push(component);
  }

  removeComponent(componentClass: Type<any>) {
    // Find the component
    const component = this.components.find((component) => component.instance instanceof componentClass);
    const componentIndex = this.components.indexOf(component);

    if (componentIndex !== -1) {
      // Remove component from both view and array
      this.container.remove(this.container.indexOf(component));
      this.components.splice(componentIndex, 1);
    }
  }
}

Notes:

  1. If you want to make it easier to remove the components later on, you can keep track of them in a local variable, see this.components. Alternatively you can loop over all the elements inside the ViewContainerRef.

  2. You have to register your component as an entry component. In your module definition register your component as an entryComponent (entryComponents: [DraggableComponent]).

Running example: https://plnkr.co/edit/mrXtE1ICw5yeIUke7wl5

For more information: https://angular.io/guide/dynamic-component-loader

Ash Belmokadem
  • 1,437
  • 10
  • 8
  • 1
    Thanks! This might be the solution I am looking for but I haven't tried it out yet. Would you be so kind as to provide a Plunker? – Marcellino Corleone Jul 06 '17 at 07:53
  • 6
    I have added a plunker to the answer. Please see [https://plnkr.co/edit/mrXtE1ICw5yeIUke7wl5](https://plnkr.co/edit/mrXtE1ICw5yeIUke7wl5). Hope this helps. – Ash Belmokadem Jul 06 '17 at 09:29
  • 1
    I edited the Plunker to include different components (foo and bar) and it works! Thank you very much! https://plnkr.co/edit/tMR2ahkb5VRpWCIh2Jvb?p=preview – Marcellino Corleone Jul 06 '17 at 10:01
  • 1
    Excellent example and code. I was searching for exactly this. I would just suggest editing this "const component = this.components.find…" to "const component = this.components.reverse().find…" in order to remove last inserted component. Or add id's to them to track specific ones. – Benjamin Sep 08 '17 at 10:27
  • 3
    There is also a tutorial about this here: https://courses.ultimateangular.com/courses/angular-pro/lectures/2170084 – Benjamin Sep 08 '17 at 13:26
  • Hi @AshBelmokadem thanks so much for this answer. Do you, or anyone, know how to update this example to support bindings? e.g. if I wanted `DraggableComponent` to bind to the `let` property on the `ng-template`, how would you do that? If you were to write it manually it would look like ` ` – Chris Jan 19 '23 at 15:56
  • `ComponentFactoryResolver` is deprecated. – Muhammed Aydogan May 02 '23 at 08:09
57

Angular v13 or above - simple way to add dynamic components to DOM

parent.component.html

<ng-template #viewContainerRef></ng-template>

parent.component.ts

@ViewChild("viewContainerRef", { read: ViewContainerRef }) vcr!: ViewContainerRef;
ref!: ComponentRef<YourChildComponent>

addChild() {
  this.ref = this.vcr.createComponent(YourChildComponent)
}

removeChild() {
  const index = this.vcr.indexOf(this.ref.hostView)
  if (index != -1) this.vcr.remove(index)
}

Angular v12 or below

I have created a demo to show the dynamic add and remove process. The parent component creates the child components dynamically and removes them.

Click for demo

Parent Component

// .ts
export class ParentComponent {
  @ViewChild("viewContainerRef", { read: ViewContainerRef })
  VCR: ViewContainerRef;

  child_unique_key: number = 0;
  componentsReferences = Array<ComponentRef<ChildComponent>>()

  constructor(private CFR: ComponentFactoryResolver) {}

  createComponent() {
    let componentFactory = this.CFR.resolveComponentFactory(ChildComponent);

    let childComponentRef = this.VCR.createComponent(componentFactory);

    let childComponent = childComponentRef.instance;
    childComponent.unique_key = ++this.child_unique_key;
    childComponent.parentRef = this;

    // add reference for newly created component
    this.componentsReferences.push(childComponentRef);
  }

  remove(key: number) {
    if (this.VCR.length < 1) return;

    let componentRef = this.componentsReferences.filter(
      x => x.instance.unique_key == key
    )[0];

    let vcrIndex: number = this.VCR.indexOf(componentRef as any);

    // removing component from container
    this.VCR.remove(vcrIndex);

    // removing component from the list
    this.componentsReferences = this.componentsReferences.filter(
      x => x.instance.unique_key !== key
    );
  }
}

// .html
<button type="button" (click)="createComponent()">
    I am Parent, Create Child
</button>
<div>
    <ng-template #viewContainerRef></ng-template>
</div>

Child Component

// .ts
export class ChildComponent {

  public unique_key: number;
  public parentRef: ParentComponent;

  constructor() {
  }

  remove_me() {
    console.log(this.unique_key)
    this.parentRef.remove(this.unique_key)
  }
}

// .html
<button (click)="remove_me()">I am a Child {{unique_key}}, click to Remove</button>
WasiF
  • 26,101
  • 16
  • 120
  • 128
  • 2
    You could replace `.filter(x => x.instance.index == index)[0]` by `.find(...)` – Dekim Nov 22 '18 at 17:39
  • Yeah, you're right but that were old days buddy when I wrote this ;) – WasiF Nov 22 '18 at 18:20
  • This solution not work with Ivy=enabled . See stackblitz.com/edit/angular-ivy-tdqan6 . It is fails on removing the component – Gringo Sep 15 '20 at 23:41
  • 5
    I made it work: https://stackblitz.com/edit/angular-ivy-k6nqph . I changed in remove method let vcrIndex: number = this.VCR.indexOf(componentRef) with let vcrIndex: number = this.VCR.indexOf(componentRef.hostView); – Gringo Sep 16 '20 at 00:37
  • 1
    I would use `.destroy()` on the component over using index of to remove it from the container. – Joe Keene Sep 18 '20 at 07:31
  • There is an update to this answer at https://stackoverflow.com/questions/64165149/how-do-i-remove-a-component-dynamically-in-angular-9 which clarifies the correct way to call ```this.VCR.indexOf()``` as the code above would not work for me wtihout this update – rossco Apr 01 '21 at 05:17