Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically ADDING and REMOVING Components in Angular

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.

like image 301
Marcellino Corleone Avatar asked Jul 06 '17 04:07

Marcellino Corleone


People also ask

What is dynamic component in Angular?

Dynamic component loadinglink This makes it impractical to use a template with a static component structure. Instead, you need a way to load a new component without a fixed reference to the component in the ad banner's template. Angular comes with its own API for loading components dynamically.

How do you destroy dynamic components?

In our dynamic component loader, it will be load component using createComponent() of ViewContainerRef. The ComponentRef of the newly created component and call the clear() method of ViewContainerRef destroys the existing view in the container. Add the destroy component.

What is dynamic rendering in Angular?

Angular gives us the mechanism to render components dynamically through View Container using ComponentFactory. To do this, we need to know the Component Type at the compile time. The most dynamic component rendering mechanism would be the one where we don't know what component will be rendered at the compile time.


2 Answers

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

like image 192
Ash Belmokadem Avatar answered Oct 11 '22 09:10

Ash Belmokadem


I have created a demo to show the dynamic add and remove process. 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> 
like image 31
WasiF Avatar answered Oct 11 '22 08:10

WasiF