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

时间:2017-07-06 04:36:12

标签: html angular typescript

目前的官方文档仅显示如何动态更改 <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 个答案:

答案 0 :(得分:34)

您尝试实现的目标可以通过使用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

注意:

  1. 如果您希望以后更容易删除组件,可以在局部变量中跟踪它们,请参阅ViewContainerRef。或者,您可以遍历entryComponents: [DraggableComponent]内的所有元素。

  2. 您必须将组件注册为条目组件。在模块定义中,将组件注册为entryComponent(.new class{ background-color:red; })。

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

    了解更多信息: https://angular.io/guide/dynamic-component-loader

答案 1 :(得分:9)

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

Click for demo

父组件

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

子组件

@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,  

  ],