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 component
s or an array
of component
s 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.
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.
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.
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.
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:
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
.
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
I have created a demo to show the dynamic add and remove process. Parent component creates the child components dynamically and removes them.
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With