简体   繁体   English

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

[英]Dynamically ADDING and REMOVING Components in Angular

The current official docs only shows how to dynamically change components within an <ng-template> tag.当前的官方文档仅展示了如何动态更改<ng-template>标签的组件。 https://angular.io/guide/dynamic-component-loader 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:我想要实现的是,假设我有 3 个组件: headersectionfooter以及以下选择器:

<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 SectionAdd 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 Section twice, 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?是否有可能在 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.请注意, ngFor不是我正在寻找的解决方案,因为它只允许向页面添加相同的组件,而不是不同的组件。

EDIT: ngIf and ngFor is not the solution I'm looking for as the templates are already predetermined.编辑: ngIfngFor不是我正在寻找的解决方案,因为模板已经预先确定。 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.我所寻找的是类似的堆栈component S或一个arraycomponent S其中,我们可以添加,删除和更改的任何索引array容易。

EDIT 2: To make it more clear, let's have another example of why ngFor does not work.编辑 2:为了更清楚,让我们举另一个例子来说明为什么ngFor不起作用。 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> .现在出现了一个新组件<app-description> ,用户希望将其插入到<app-editor> ngFor works only if there is one same component that I want to loop over and over. ngFor仅在我想一遍又一遍地循环一个相同的组件ngFor起作用。 But for different components, ngFor fails here.但是对于不同的组件, ngFor在这里失败了。

What you're trying to achieve can be done by creating components dynamically using the ComponentFactoryResolver and then injecting them into a ViewContainerRef .您可以通过使用ComponentFactoryResolver动态创建组件然后将它们注入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:笔记:

  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 .如果您希望稍后更轻松地删除组件,您可以在局部变量中跟踪它们,请参阅this.components Alternatively you can loop over all the elements inside the ViewContainerRef .或者,您可以遍历ViewContainerRef内的所有元素。

  2. You have to register your component as an entry component.您必须将您的组件注册为入口组件。 In your module definition register your component as an entryComponent ( entryComponents: [DraggableComponent] ).在您的模块定义中,将您的组件注册为 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

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父组件

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM