Html 在 Angular 中动态添加和移除组件

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/44939878/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-29 14:46:25  来源:igfitidea点击:

Dynamically ADDING and REMOVING Components in Angular

htmlangulartypescript

提问by Marcellino Corleone

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

当前的官方文档只展示了如何动态更改标签的组件<ng-template>https://angular.io/guide/dynamic-component-loader

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

我想要实现的是,假设我有 3 个组件:header, section, 和footer以下选择器:

<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

再有6个按钮,这将增加或删除每个组件:Add HeaderAdd Section,和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:

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

<app-header>

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

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

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

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

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

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

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

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

EDIT: ngIfand ngForis 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 arrayof components where we can add, remove, and change any index of the arrayeasily.

编辑:ngIf并且ngFor不是我正在寻找的解决方案,因为模板已经预先确定。我所寻找的是类似的堆栈component秒或一arraycomponentS其中,我们可以添加,删除和更改的任何索引array容易。

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

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

<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>. ngForworks only if there is one same component that I want to loop over and over. But for different components, ngForfails here.

现在出现了一个新组件 ,<app-description>用户希望将其插入到 和 之间<app-editor>ngFor只有当我想一遍又一遍地循环一个相同的组件时才有效。但是对于不同的组件,ngFor这里失败了。

回答by Ash Belmokadem

What you're trying to achieve can be done by creating components dynamically using the ComponentFactoryResolverand 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.

您可以通过使用 动态创建组件ComponentFactoryResolver然后将它们注入到ViewContainerRef. 动态执行此操作的一种方法是将组件的类作为将创建和注入组件的函数的参数传递。

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]).

  1. 如果您希望稍后更轻松地移除组件,您可以在局部变量中跟踪它们,请参阅this.components。或者,您可以循环遍历ViewContainerRef.

  2. 您必须将您的组件注册为入口组件。在您的模块定义中,将您的组件注册为 entryComponent ( entryComponents: [DraggableComponent])。

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

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

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

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

回答by WasiF

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

父组件

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

add references to the app.module.ts

添加对app.module.ts 的引用

@NgModule({
  declarations: [

    ParentComponent,
    ChildComponent

  ],
  imports: [

    //if using routing then add like so
    RouterModule.forRoot([
      { path: '', component: ParentComponent }
    ]),

  ],
  entryComponents: [

    ChildComponent,  

  ],