首页 文章

动态添加和删除Angular中的组件

提问于
浏览
20

目前的官方文档仅显示如何动态 change 组件 <ng-template> 标签 . https://angular.io/guide/dynamic-component-loader

我想要实现的是,假设我有3个组件: headersectionfooter 以及以下选择器:

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

然后有6个按钮可以添加或删除每个组件: Add HeaderAdd SectionAdd Footer

当我单击 Add Header 时,该页面会将 <app-header> 添加到呈现它的页面,因此该页面将包含:

<app-header>

然后如果我单击 Add Section 两次,该页面现在将包含:

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

如果我单击 Add Footer ,页面将包含所有这些组件:

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

是否有可能在Angular中实现这一目标?请注意 ngFor 不是我正在寻找的解决方案,因为它只允许向页面添加相同的组件,而不是不同的组件 .

编辑:ngIf和ngFor不是我正在寻找的解决方案,因为模板已经预先确定 . 我正在寻找的东西就像是一堆组件或一组组件,我们可以轻松地添加,删除和更改数组的任何索引 .

编辑2:为了更清楚,让我们有另一个例子说明为什么ngFor不起作用 . 假设我们有以下组件:

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

现在出现了一个新组件 <app-description> ,用户希望在其中插入 <app-editor> . ngFor只有在我想要循环遍历的同一个组件时才有效 . 但是对于不同的组件,ngFor在这里失败了 .

2 回答

  • 5

    您尝试实现的目标可以通过使用 ComponentFactoryResolver 动态创建组件然后将它们注入 ViewContainerRef 来完成 . 动态执行此操作的一种方法是将组件的类作为函数的参数传递,该函数将创建并注入组件 .

    见下面的例子:

    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);
        }
      }
    }
    

    笔记:

    • 如果您希望以后更容易删除组件,可以在局部变量中跟踪它们,请参阅 this.components . 或者,您可以遍历 ViewContainerRef 中的所有元素 .

    • 您必须将组件注册为条目组件 . 在模块定义中,将组件注册为entryComponent( entryComponents: [DraggableComponent] ) .

    运行示例:https://plnkr.co/edit/mrXtE1ICw5yeIUke7wl5

    有关更多信息:https://angular.io/guide/dynamic-component-loader

  • 28

    我创建了一个演示来显示动态添加和删除过程 . 父组件动态创建子组件并将其删除 .

    点击进行演示

    Parent Component

    import { ComponentRef, ComponentFactoryResolver, ViewContainerRef, ViewChild, Component } from "@angular/core";
    
    @Component({
        selector: 'parent',
        template: `
        <button type="button" (click)="createComponent()">
            Create Child
        </button>
        <div>
            <ng-template #viewContainerRef></ng-template>
        </div>
      `
    })
    export class ParentComponent implements myinterface {
    
        @ViewChild('viewContainerRef', { read: ViewContainerRef }) VCR: ViewContainerRef;
    
        //manually indexing the child components for better removal
        //although there is by-default indexing but it is being avoid for now
        //so index is a unique property here to identify each component individually.
        index: number = 0;
    
        // to store references of dynamically created components
        componentsReferences = [];
    
        constructor(private CFR: ComponentFactoryResolver) {
        }
    
        createComponent() {
    
            let componentFactory = this.CFR.resolveComponentFactory(ChildComponent);
            let componentRef: ComponentRef<ChildComponent> = this.VCR.createComponent(componentFactory);
            let currentComponent = componentRef.instance;
    
            currentComponent.selfRef = currentComponent;
            currentComponent.index = ++this.index;
    
            // prividing parent Component reference to get access to parent class methods
            currentComponent.compInteraction = this;
    
            // add reference for newly created component
            this.componentsReferences.push(componentRef);
        }
    
        remove(index: number) {
    
            if (this.VCR.length < 1)
                return;
    
            let componentRef = this.componentsReferences.filter(x => x.instance.index == index)[0];
            let component: ChildComponent = <ChildComponent>componentRef.instance;
    
            let vcrIndex: number = this.VCR.indexOf(componentRef)
    
            // removing component from container
            this.VCR.remove(vcrIndex);
    
            this.componentsReferences = this.componentsReferences.filter(x => x.instance.index !== index);
        }
    }
    

    Child Component

    @Component({
        selector: 'child',
        template: `
        <div>
        <h1 (click)="removeMe(index)">I am a Child, click to Remove</h1>
        </div>
        `
    })
    export class ChildComponent {
    
        public index: number;
        public selfRef: ChildComponent;
    
        //interface for Parent-Child interaction
        public compInteraction: myinterface;
    
        constructor() {
        }
    
        removeMe(index) {
            this.compInteraction.remove(index)
        }
    }
    
    // Interface
    export interface myinterface {
        remove(index: number);
    }
    

    添加对 app.module.ts 的引用

    @NgModule({
      declarations: [
    
        ParentComponent,
        ChildComponent
    
      ],
      imports: [
    
        //if using routing then add like so
        RouterModule.forRoot([
          { path: '', component: ParentComponent }
        ]),
    
      ],
      entryComponents: [
    
        ChildComponent,  
    
      ],
    

相关问题