在 Angular7 应用程序中使用 ComponentFactoryResolver 是个好主意吗?
Is it good idea to use ComponentFactoryResolver in Angular7 application?
我想创建一个 angular 7 web 应用程序来动态加载不同的组件,如本官方文档中所示:
https://angular.io/guide/dynamic-component-loader
但我不确定使用 ComponentFactoryResolver
是否是个好主意。
没用过,不知道稳定不,性能也不知道
我想要一些关于它的意见,如果有人知道任何替代方案。
我不想使用 native innerHTML
我正在尝试创建一个带有动态步骤的自定义和通用向导。
该向导有
- header分量
- 向导步骤
- 一个“容器”。现在我正在使用 ng-template 来显示每个步骤的内容(一个单独的组件,在某些情况下是一个复杂的组件)
- 向导按钮(下一步和上一步)和最后一步操作按钮,如保存等
步骤是动态的。基于一些业务逻辑,例如用户在前面步骤中的输入。
我当前的实现:
我将只展示我使用 ComponentFactoryResolver
的部分,以使其易于理解和阅读:)
export class WizComponent implements OnInit {
public wizContentItems: WizContentItem[] = undefined;
public currentContentItem: WizContentItem = undefined;
public currentContentItemNumber: number = -1;
public currentWizContentComponent: WizContentComponent = undefined;
private componentRef: any;
@Output() public onStepChanged = new EventEmitter<StepPosition>();
private _position: StepPosition = StepPosition.First;
constructor(private componentFactoryResolver: ComponentFactoryResolver, private viewContainerRef: ViewContainerRef) { }
public ngOnInit() {
}
public onSelectStep(contentItem: WizContentItem) {
console.log("step was clicked");
console.log(contentItem);
if (this.currentContentItem !== undefined &&
!this.validateStep(this.currentContentItem)) {
return;
}
if (this.currentWizContentComponent !== undefined ) {
this.currentContentItem.stepProgressStatus = this.currentWizContentComponent.stepProgressStatus;
}
contentItem.stepState = StepState.Active;
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(contentItem.component);
this.viewContainerRef.clear();
this.componentRef = this.viewContainerRef.createComponent(componentFactory);
(<WizContentComponent>this.componentRef.instance).data = contentItem.data;
(<WizContentComponent>this.componentRef.instance).stepState = contentItem.stepState;
this.currentWizContentComponent = (<WizContentComponent>this.componentRef.instance);
if (this.currentContentItem != null) {
this.currentContentItem.stepState = StepState.Empty;
}
this.currentContentItem = contentItem;
this.currentContentItem.stepState = StepState.Active;
// Get currentContentItemNumber based currentContentItem
this.currentContentItemNumber = this.wizContentItems.findIndex(wizContentItem => wizContentItem === this.currentContentItem);
this.stepChanged();
}
public onNextClick(event: Event) {
if ((this.currentContentItemNumber + 1) < this.wizContentItems.length) {
let nextContentItem = this.wizContentItems[this.currentContentItemNumber + 1];
if (nextContentItem.stepState === StepState.Disabled) {
nextContentItem = this.getNextActiveItem(this.currentContentItemNumber + 1);
}
if (nextContentItem != null) {
this.onSelectStep(nextContentItem);
}
}
}
public onPreviousClick(event: Event) {
if ((this.currentContentItemNumber - 1) >= 0) {
let previousContentItem = this.wizContentItems[this.currentContentItemNumber - 1];
if (previousContentItem.stepState === StepState.Disabled) {
previousContentItem = this.getPreviousActiveItem(this.currentContentItemNumber - 1);
}
if (previousContentItem !== null) {
this.onSelectStep(previousContentItem);
}
}
}
public getCurrentStepPosition(): StepPosition {
return this._position;
}
private validateStep(contentItem: WizContentItem): boolean {
return (<WizContentImplComponent>this.componentRef.instance).isValid();
}
private stepChanged(): void {
this._position = undefined;
if (this.currentContentItemNumber <= 0) {
this._position = StepPosition.First;
} else if (this.currentContentItemNumber >= this.wizContentItems.length) {
this._position = StepPosition.Last;
} else {
this._position = StepPosition.Middle;
}
if ((<WizContentComponent>this.componentRef.instance).isSummary) {
this._position = StepPosition.Summary;
}
this.onStepChanged.emit(this._position);
}
private getNextActiveItem(itemNumber: number): WizContentItem {
if (this.wizContentItems.length <= (itemNumber + 1)) {
return null;
}
let nextContentItem = null;
for (let i = (itemNumber); i < this.wizContentItems.length; i++) {
if ( this.wizContentItems[i].stepState !== StepState.Disabled ) {
nextContentItem = this.wizContentItems[i];
break;
}
}
return nextContentItem;
}
private getPreviousActiveItem(itemNumber: number): WizContentItem {
if ((itemNumber - 1) < 0 ) {
return null;
}
let previousContentItem = null;
for (let i = (itemNumber - 1); i >= 0; i--) {
if ( this.wizContentItems[i].stepState !== StepState.Disabled ) {
previousContentItem = this.wizContentItems[i];
break;
}
}
return previousContentItem;
}
}
谢谢!!
是的,使用 ComponentFactoryResolver
很好,这就是它在官方文档中的原因。自 Angular 2 以来它在内部是稳定的。它没有显着的性能影响。
许多 Angular 库也在内部使用它 Angular Material library。
检查 Portal inside the Component Development Kit (CDK) and its source in GitHub,您可以在其中看到它用于显示其中的动态内容。
关于您的问题是使用 NgSwitch
还是使用 ComponetFactoryResolver
创建组件更好,这个问题很难回答,因为这取决于您尝试做什么,而您没有解释到底是什么是你的场景。我想说的是,在大多数情况下,您应该使用 ComponentFactoryResolver
,因为它允许您动态添加任何组件,并且您没有一个大型组件,其中包含所有可能的动态组件的巨大 NgSwitch
。只有在您的动态组件数量很少并且您不希望添加新组件的情况下,使用 NgSwitch
.
可能更容易创建它们
作为对上一个答案的补充,为了更好地比较这两种方法,可能值得添加一些关于每种情况下发生的事情的细节。
使用 FactoryResolver 服务 'create' 组件的步骤:
- 使用
resolveComponentFactory()
实例化组件 class
方法:此方法将组件类型作为参数,并且
寻找对应的 'component factory'.
Nb:组件工厂是由 Angular 为每个声明的组件创建的 classes,目的是实例化新组件
- 'append' 使用
createComponent()
视图的新组件
ViewContainerRef
class 的方法
相关信息:
https://angular.io/guide/dynamic-component-loader#resolving-components
结构指令(ngIf
、ngSwitch
...)时应用的步骤'creates'一个组件:
- 该指令使用提供的模板创建嵌入式视图。
为此,它还使用
ViewContainerRef
class(
createEmbeddedView()
方法)。
- 如果此视图包含组件选择器,Angular
实例化一个新组件 class,也使用相应的
工厂,将附加到视图。
=> 这两种方法大致经历了相同的步骤(实际上 'structural directive' 方法增加了一个额外的步骤,创建一个嵌入视图,我认为,可以忽略不计)。
因此,在我看来,从两个选项中选择一个的最有价值的原因是用例,我将总结如下:
结构指令 (ngIf
, ngSwitch
...):
- 组件少时有用
FactoryResolver 服务:
- 避免一长串组件(如前一个答案中所述)
- 更好的关注点分离(模板或父组件可能不需要知道可能被实例化的所有组件的列表)
- 需要延迟加载动态组件(我推荐这个以获取更多信息:https://blog.angularindepth.com/here-is-what-you-need-to-know-about-dynamic-components-in-angular-ac1e96167f9e)
我想创建一个 angular 7 web 应用程序来动态加载不同的组件,如本官方文档中所示: https://angular.io/guide/dynamic-component-loader
但我不确定使用 ComponentFactoryResolver
是否是个好主意。
没用过,不知道稳定不,性能也不知道
我想要一些关于它的意见,如果有人知道任何替代方案。
我不想使用 native innerHTML
我正在尝试创建一个带有动态步骤的自定义和通用向导。 该向导有
- header分量
- 向导步骤
- 一个“容器”。现在我正在使用 ng-template 来显示每个步骤的内容(一个单独的组件,在某些情况下是一个复杂的组件)
- 向导按钮(下一步和上一步)和最后一步操作按钮,如保存等
步骤是动态的。基于一些业务逻辑,例如用户在前面步骤中的输入。
我当前的实现:
我将只展示我使用 ComponentFactoryResolver
的部分,以使其易于理解和阅读:)
export class WizComponent implements OnInit {
public wizContentItems: WizContentItem[] = undefined;
public currentContentItem: WizContentItem = undefined;
public currentContentItemNumber: number = -1;
public currentWizContentComponent: WizContentComponent = undefined;
private componentRef: any;
@Output() public onStepChanged = new EventEmitter<StepPosition>();
private _position: StepPosition = StepPosition.First;
constructor(private componentFactoryResolver: ComponentFactoryResolver, private viewContainerRef: ViewContainerRef) { }
public ngOnInit() {
}
public onSelectStep(contentItem: WizContentItem) {
console.log("step was clicked");
console.log(contentItem);
if (this.currentContentItem !== undefined &&
!this.validateStep(this.currentContentItem)) {
return;
}
if (this.currentWizContentComponent !== undefined ) {
this.currentContentItem.stepProgressStatus = this.currentWizContentComponent.stepProgressStatus;
}
contentItem.stepState = StepState.Active;
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(contentItem.component);
this.viewContainerRef.clear();
this.componentRef = this.viewContainerRef.createComponent(componentFactory);
(<WizContentComponent>this.componentRef.instance).data = contentItem.data;
(<WizContentComponent>this.componentRef.instance).stepState = contentItem.stepState;
this.currentWizContentComponent = (<WizContentComponent>this.componentRef.instance);
if (this.currentContentItem != null) {
this.currentContentItem.stepState = StepState.Empty;
}
this.currentContentItem = contentItem;
this.currentContentItem.stepState = StepState.Active;
// Get currentContentItemNumber based currentContentItem
this.currentContentItemNumber = this.wizContentItems.findIndex(wizContentItem => wizContentItem === this.currentContentItem);
this.stepChanged();
}
public onNextClick(event: Event) {
if ((this.currentContentItemNumber + 1) < this.wizContentItems.length) {
let nextContentItem = this.wizContentItems[this.currentContentItemNumber + 1];
if (nextContentItem.stepState === StepState.Disabled) {
nextContentItem = this.getNextActiveItem(this.currentContentItemNumber + 1);
}
if (nextContentItem != null) {
this.onSelectStep(nextContentItem);
}
}
}
public onPreviousClick(event: Event) {
if ((this.currentContentItemNumber - 1) >= 0) {
let previousContentItem = this.wizContentItems[this.currentContentItemNumber - 1];
if (previousContentItem.stepState === StepState.Disabled) {
previousContentItem = this.getPreviousActiveItem(this.currentContentItemNumber - 1);
}
if (previousContentItem !== null) {
this.onSelectStep(previousContentItem);
}
}
}
public getCurrentStepPosition(): StepPosition {
return this._position;
}
private validateStep(contentItem: WizContentItem): boolean {
return (<WizContentImplComponent>this.componentRef.instance).isValid();
}
private stepChanged(): void {
this._position = undefined;
if (this.currentContentItemNumber <= 0) {
this._position = StepPosition.First;
} else if (this.currentContentItemNumber >= this.wizContentItems.length) {
this._position = StepPosition.Last;
} else {
this._position = StepPosition.Middle;
}
if ((<WizContentComponent>this.componentRef.instance).isSummary) {
this._position = StepPosition.Summary;
}
this.onStepChanged.emit(this._position);
}
private getNextActiveItem(itemNumber: number): WizContentItem {
if (this.wizContentItems.length <= (itemNumber + 1)) {
return null;
}
let nextContentItem = null;
for (let i = (itemNumber); i < this.wizContentItems.length; i++) {
if ( this.wizContentItems[i].stepState !== StepState.Disabled ) {
nextContentItem = this.wizContentItems[i];
break;
}
}
return nextContentItem;
}
private getPreviousActiveItem(itemNumber: number): WizContentItem {
if ((itemNumber - 1) < 0 ) {
return null;
}
let previousContentItem = null;
for (let i = (itemNumber - 1); i >= 0; i--) {
if ( this.wizContentItems[i].stepState !== StepState.Disabled ) {
previousContentItem = this.wizContentItems[i];
break;
}
}
return previousContentItem;
}
}
谢谢!!
是的,使用 ComponentFactoryResolver
很好,这就是它在官方文档中的原因。自 Angular 2 以来它在内部是稳定的。它没有显着的性能影响。
许多 Angular 库也在内部使用它 Angular Material library。 检查 Portal inside the Component Development Kit (CDK) and its source in GitHub,您可以在其中看到它用于显示其中的动态内容。
关于您的问题是使用 NgSwitch
还是使用 ComponetFactoryResolver
创建组件更好,这个问题很难回答,因为这取决于您尝试做什么,而您没有解释到底是什么是你的场景。我想说的是,在大多数情况下,您应该使用 ComponentFactoryResolver
,因为它允许您动态添加任何组件,并且您没有一个大型组件,其中包含所有可能的动态组件的巨大 NgSwitch
。只有在您的动态组件数量很少并且您不希望添加新组件的情况下,使用 NgSwitch
.
作为对上一个答案的补充,为了更好地比较这两种方法,可能值得添加一些关于每种情况下发生的事情的细节。
使用 FactoryResolver 服务 'create' 组件的步骤:
- 使用
resolveComponentFactory()
实例化组件 class 方法:此方法将组件类型作为参数,并且 寻找对应的 'component factory'.
Nb:组件工厂是由 Angular 为每个声明的组件创建的 classes,目的是实例化新组件 - 'append' 使用
createComponent()
视图的新组件ViewContainerRef
class 的方法
相关信息: https://angular.io/guide/dynamic-component-loader#resolving-components
结构指令(ngIf
、ngSwitch
...)时应用的步骤'creates'一个组件:
- 该指令使用提供的模板创建嵌入式视图。
为此,它还使用
ViewContainerRef
class(createEmbeddedView()
方法)。 - 如果此视图包含组件选择器,Angular 实例化一个新组件 class,也使用相应的 工厂,将附加到视图。
=> 这两种方法大致经历了相同的步骤(实际上 'structural directive' 方法增加了一个额外的步骤,创建一个嵌入视图,我认为,可以忽略不计)。
因此,在我看来,从两个选项中选择一个的最有价值的原因是用例,我将总结如下:
结构指令 (ngIf
, ngSwitch
...):
- 组件少时有用
FactoryResolver 服务:
- 避免一长串组件(如前一个答案中所述)
- 更好的关注点分离(模板或父组件可能不需要知道可能被实例化的所有组件的列表)
- 需要延迟加载动态组件(我推荐这个以获取更多信息:https://blog.angularindepth.com/here-is-what-you-need-to-know-about-dynamic-components-in-angular-ac1e96167f9e)