在 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 个组件:header
、section
和 footer
,并带有以下选择器:
<app-header>
<app-section>
<app-footer>
然后有 6 个按钮可以添加或删除每个组件:Add Header
、Add Section
和 Add 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
不是我正在寻找的解决方案,因为模板已经预先确定。我正在寻找的是类似于component
s 的堆栈或component
s 的array
,我们可以在其中轻松添加、删除和更改array
的任何索引。
编辑 2:为了更清楚,让我们再举一个例子说明为什么 ngFor
不起作用。假设我们有以下组件:
<app-header>
<app-introduction>
<app-camera>
<app-editor>
<app-footer>
现在出现了一个新组件<app-description>
,用户希望将其插入到<app-editor>
之间。 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 => 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 2 组件动态添加 innerHTML