在 Angular7 应用程序中使用 ComponentFactoryResolver 是个好主意吗?

Posted

技术标签:

【中文标题】在 Angular7 应用程序中使用 ComponentFactoryResolver 是个好主意吗?【英文标题】:Is it good idea to use ComponentFactoryResolver in Angular7 application? 【发布时间】:2019-10-19 04:30:38 【问题描述】:

我想创建一个动态加载不同组件的 Angular 7 Web 应用程序,如官方文档中所示: https://angular.io/guide/dynamic-component-loader

但我不确定使用ComponentFactoryResolver 是否是个好主意。 没用过,不知道稳定不,性能也不知道。

我想对此提出一些意见,如果有人知道任何替代方案。 我不想用native innerhtml

我正在尝试使用动态步骤创建自定义和通用向导。 这个向导有

标头组件 向导步骤 一个“容器”。现在我正在使用 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;
  

谢谢!!

【问题讨论】:

您能否更新您的问题。描述为什么需要动态创建组件。您这样做是为了解决什么问题,并举例说明这在您的应用程序中会是什么样子。就目前而言,您已经选择了一个解决方案,但没有描述问题,然后询问它是否是一个好的解决方案。 感谢@Reactgular 的评论和大家的回答。我会用更多细节更新我的问题。实际上,我正在尝试使用动态步骤创建自定义向导。每个步骤都将是一个单独的组件,某些情况下会使组件复杂化。 @A.Zalonis 你有这个代码的机会。如果是这样,请分享给像我这样的其他人,以获取灵感回购或沙箱链接会起作用:) 【参考方案1】:

是的,使用ComponentFactoryResolver 很好,这就是它在官方文档中的原因。自 Angular 2 以来,它就在内部是稳定的。它没有显着的性能影响。

许多 Angular 库在内部也使用它 Angular Material library。 检查Portal inside the Component Development Kit (CDK) 及其source in GitHub,您可以在其中看到它用于在其中显示动态内容。

关于您的问题是使用NgSwitch 还是使用ComponetFactoryResolver 创建组件更好,这很难回答,因为这取决于您要做什么,并且您没有解释您的具体情况。我想说,在大多数情况下,您应该使用ComponentFactoryResolver,因为它允许您动态添加任何组件,并且对于所有可能的动态组件,您没有一个带有巨大NgSwitch 的大组件。只有在您拥有非常少量的动态组件并且您不希望添加新组件的情况下,使用NgSwitch 创建它们可能会更容易。

【讨论】:

感谢@AlesD 的回答。使用 ComponentFactoryResolver 是否可以使用 Observable 数据绑定?是清晰的角度方式来做员工还是更像是一种解决方法?谢谢 您可以使用async pipe 在任何组件中进行 Observable 数据绑定,无论组件是如何创建的。如果您有 Observables,这是一种适当的角度做事方式。使用工厂创建,您需要在组件上具有@Input 属性以进行绑定,然后在使用工厂创建组件后在代码中设置它们。 好的,太好了!非常感谢@AlesD【参考方案2】:

作为对上一个答案的补充,为了更好地比较这两种方法,可能值得添加一些关于每种情况的详细信息。

使用 FactoryResolver 服务“创建”组件的步骤:

    使用resolveComponentFactory() 实例化组件类 方法:此方法将组件类型作为参数,以及 查找相应的“组件工厂”。注意:组件工厂是 Angular 为每个声明的组件创建的类,目的是实例化新组件 使用createComponent() 将新组件“追加”到视图中 ViewContainerRef 类的方法

有关信息: https://angular.io/guide/dynamic-component-loader#resolving-components

结构指令ngIfngSwitch...)“创建”组件时应用的步骤:

    该指令使用提供的模板创建一个嵌入视图。 为此,它还使用ViewContainerRef 类( createEmbeddedView() 方法)。 如果此视图包含组件选择器,Angular 实例化一个新的组件类,也使用相应的 工厂,它将被附加到视图中。

=>这两种方法的步骤大致相同(实际上“结构指令”方法增加了一个额外的步骤,即创建嵌入式视图,我认为这可以忽略不计)。

因此,在我看来,从两个选项中选择一个的最有价值的理由是用例,我将其总结如下:

结构指令ngIfngSwitch...):

在组件很少时很有用

FactoryResolver 服务:

避免一长串组件(如上一个答案中所述) 更好地分离关注点(模板或父组件可能不需要知道可能被实例化的所有组件的列表) 需要延迟加载动态组件(我推荐这个以获得更多信息:https://blog.angularindepth.com/here-is-what-you-need-to-know-about-dynamic-components-in-角-ac1e96167f9e)

【讨论】:

以上是关于在 Angular7 应用程序中使用 ComponentFactoryResolver 是个好主意吗?的主要内容,如果未能解决你的问题,请参考以下文章

使用 apache 重写的 Angular7 无法正常工作

在后端使用NodeJS的Put方法不更新Angular7中的MongoDB

如何在Angular7中使用另一个组件中的变量

如何在两个组件之间使用Angular7(角度材料)拖放

如何在同一个项目中创建具有 Angular7 + WebAPI + AzureAD 的 .NET Core 应用程序

angular7中使用iframe来加载外部页面