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

Posted

技术标签:

【中文标题】在 Angular 中动态添加和删除组件【英文标题】:Dynamically ADDING and REMOVING Components in Angular 【发布时间】:2017-12-09 22:16:26 【问题描述】:

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

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

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

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

当我点击Add Header时,页面会将&lt;app-header&gt;添加到呈现它的页面中,因此页面将包含:

<app-header>

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

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

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

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

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

编辑:ngIfngFor 不是我正在寻找的解决方案,因为模板已经预先确定。我正在寻找的是类似于components 的堆栈或components 的array,我们可以在其中轻松添加、删除和更改array 的任何索引。

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

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

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

【问题讨论】:

如果它们是已知的,如您的示例所示,您可以在页眉和页脚上使用 *ngIf 并在部分上使用 *ngFor。 @DeborahK ngIf 和 ngFor 不是我正在寻找的解决方案,因为模板已经预先确定。我正在寻找的是类似于组件堆栈或组件数组的东西,我们可以在其中轻松添加、删除和更改数组的任何索引。 如果您想添加不同类型的组件,这也可能会有所帮助***.com/questions/36325212/… @GünterZöchbauer 谢谢!当我了解 ngComponentOutlet 时,这可能特别有用 ngComponentOutlet 仍然缺少一些基本功能。很久以来就有一个拉取请求来解决这些问题,但它尚未合并 AFAIK。因此从动态添加的组件读取和传递值并不能很好地工作。 【参考方案1】:

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

Click for demo

父组件

// .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>

子组件

// .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>

【讨论】:

你可以用.find(...)替换.filter(x =&gt; x.instance.index == index)[0] 是的,你是对的,但是当我写这篇文章时,那是过去的朋友;) 此解决方案不适用于 Ivy=enabled 。请参阅 stackblitz.com/edit/angular-ivy-tdqan6。移除组件失败 我成功了:stackblitz.com/edit/angular-ivy-k6nqph。我更改了 remove 方法 let vcrIndex: number = this.VCR.indexOf(componentRef) 与 let vcrIndex: number = this.VCR.indexOf(componentRef.hostView); 我会在组件上使用.destroy(),而不是使用 index of 从容器中删除它。【参考方案2】:

您可以通过使用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

【讨论】:

谢谢!这可能是我正在寻找的解决方案,但我还没有尝试过。您能提供一个 Plunker 吗? 我在答案中添加了一个 plunker。请参阅https://plnkr.co/edit/mrXtE1ICw5yeIUke7wl5。希望这会有所帮助。 我编辑了 Plunker 以包含不同的组件(foo 和 bar),它可以工作!非常感谢! plnkr.co/edit/tMR2ahkb5VRpWCIh2Jvb?p=preview 优秀的例子和代码。我正在寻找这个。我只是建议将这个“const component = this.components.find...”编辑为“const component = this.components.reverse().find...”,以便删除最后插入的组件。或者向它们添加 id 以跟踪特定的。 这里也有关于这个的教程:courses.ultimateangular.com/courses/angular-pro/lectures/…

以上是关于在 Angular 中动态添加和删除组件的主要内容,如果未能解决你的问题,请参考以下文章

Angular 表单:如何动态添加/删除子表单组件

Angular 2:向动态创建的组件添加指令

角度 5 - 如何从 dom 中删除动态添加的组件

如何使用 Angular 2 组件动态添加 innerHTML

使用 Angular,如何在点击时动态添加组件? (不止一个)

使用gridstack和angular2动态添加组件? Jquery $.parseHTML 不起作用