繁体   English   中英

使用 ComponentFactoryResolver 导入 Angular 动态组件:传入的类型不是 ComponentType

[英]Angular dynamic component import with ComponentFactoryResolver: Type passed in is not ComponentType

我正在尝试减少我的 Angular 应用程序的主包大小,其中我最近的努力涉及使模块化组件动态加载其“子组件”,而不是通过静态导入它们。 文件夹结构(和父子关系)如下所示:

io-input/io-input.component
├─ date/date.component
├─ ... more components ...
└─ ... more components ...

它位于 Angular 库中的 Angular 10 项目中。 该库作为模块导入到同一 Angular 项目中的 Angular 应用程序中。 当我使用ng serve编译应用程序时,该应用程序会遇到围绕io-input组件(特别是动态导入子组件的代码)的错误。

ERROR Error: ASSERTION ERROR: Type passed in is not ComponentType, it does not have 'ɵcmp' property.

我的问题是:我的新动态导入方法有什么问题? 我在下面提供了我的方法(动态导入之前和动态导入之后)。

旧情况:有效的静态导入

如果我静态导入io-input.component所有组件,则一切正常。 我将ComponentFactoryResolverresolveComponentFactory ,并根据我通过@Input()提供的配置,将生成所选组件的新实例并将其插入到父组件的专用ViewContainer 例子:

import { DateComponent } from './date/date.component';
import { 
  Component, ComponentFactoryResolver,
  AfterViewInit, OnDestroy,
  Input, Output,
  EventEmitter, ViewEncapsulation,
  ViewChild, ViewContainerRef, HostBinding, ElementRef, Injector
} from '@angular/core';

// ... more imports/interfaces/services/etc. here ...

@Component({
  selector: 'app-io-input',
  templateUrl: './io-input.component.html',
  styleUrls: ['./io-input.component.scss'],
  encapsulation : ViewEncapsulation.None,
  providers: [
     // ... some services here
  ]
})
export class IoInputComponent implements AfterViewInit, OnDestroy, MatFormFieldControl<any>, ControlValueAccessor {
  
  @Input() inputConfig : InputConfigInterface;
  private componentRef : any;
  // ... more variables ...
  
  constructor(
    private componentFactoryResolver: ComponentFactoryResolver
  ) {
  }
  ngAfterViewInit() {
    let comp : any;
    switch(this.inputConfig.type){
      case "date":
        comp = DateComponent;
      break;
      // more cases for other components
      // a default (fallback) component in a default case
    }
    const componentFactory = this.componentFactoryResolver.resolveComponentFactory(comp);
    this.componentRef = this.container.createComponent(componentFactory);
  }
}

新情况:动态导入组件

当我动态加载组件时,出现错误:

ERROR Error: ASSERTION ERROR: Type passed in is not ComponentType, it does not have 'ɵcmp' property.

带有动态导入的更新代码如下所示:

// import { DateComponent } from './date/date.component'; // not used anymore
import { 
  Component, ComponentFactoryResolver,
  AfterViewInit, OnDestroy,
  Input, Output,
  EventEmitter, ViewEncapsulation,
  ViewChild, ViewContainerRef, HostBinding, ElementRef, Injector
} from '@angular/core';

// ... more imports/interfaces/services/etc. here ...

@Component({
  selector: 'app-io-input',
  templateUrl: './io-input.component.html',
  styleUrls: ['./io-input.component.scss'],
  encapsulation : ViewEncapsulation.None,
  providers: [
    // ... some services here
  ]
})
export class IoInputComponent implements AfterViewInit, OnDestroy, MatFormFieldControl<any>, ControlValueAccessor {
  
  @Input() inputConfig : InputConfigInterface;
  private componentRef : any;
  // ... more variables ...
  
  constructor(
    private componentFactoryResolver: ComponentFactoryResolver
  ) {
  }
  ngAfterViewInit() {
    let comp : any;
    switch(this.inputConfig.type){
      case "date":
        comp = import('./date/date.component').then(m => { // new: dynamically imported component
            debugger;                                      // allowed me to know what "m" looks like
            return m.DateComponent;                        // new: dynamically imported component
        });                                                // new: dynamically imported component
      break;
      // more cases for other dynamically imported components
      // a default (fallback) component in a default case
    }
    debugger;                                              // allowed me to know what "comp" looks like
    const componentFactory = this.componentFactoryResolver.resolveComponentFactory(comp);
    this.componentRef = this.container.createComponent(componentFactory);
  }
}

如果我在运行时使用debugger;检查我的代码debugger; , m填充了一个具有属性DateComponent的对象,该对象似乎确实有一个名为"ɵcmp"的属性。 我在动态导入组件的方法中做错了什么吗?

在动态导入 DateComponent 时检查调试器断点

正如@MikeOne 所暗示的那样,Nethanel Basal 的文章提供了一个很好的方向。 我强烈建议任何使用动态组件加载的人阅读它。 这里有一些链接。

因为在我的情况下,我更多地以参数化的方式工作,所以 Nethanel 文章中的基本示例还不够模块化。

一些外卖

  1. 我需要确保我的模块化设置的输入是正确的; 根据参数从一组组件中加载一个组件需要您考虑要支持的任何类型! 否则编译器会抛出错误。
  2. 当使用动态import() webpack-function 加载组件时,您需要以异步方式处理结果(例如使用Promise.then()回调)。 在您的打字中也要考虑到这一点!

这些要点使我定义了一个具有以下类型的变量:

let comp : Promise<Type<DateComponent | PlainTextComponent | FileComponent | ToggleComponent>>;

在这种情况下,组件类型列表并非详尽无遗。 只需在此处定义您需要的任何组件。 Type<NAMEOFCOMPONENT>只是ComponentType一个接口,类型化为您要使用的组件。 Type必须从@angular/core导入才能完成这项工作。

这是我更新的代码。 请注意,这可能只是一个起点; 这里可能还有更多的改进空间! 想一想:清理代码并使代码本身更有效率。

import { DateComponent } from './date/date.component';
import { PlainTextComponent } from './plain-text/plain-text.component';
import { ToggleComponent } from './toggle/toggle.component';

// The components are imported for type definition only.

import { 
  Component, ComponentFactoryResolver,
  AfterViewInit, OnDestroy,
  Input, Output,
  EventEmitter, ViewEncapsulation,
  ViewChild, ViewContainerRef, HostBinding, ElementRef, Injector, Type
} from '@angular/core';

// ... more imports/interfaces/services/etc. here ...

@Component({
  selector: 'app-io-input',
  templateUrl: './io-input.component.html',
  styleUrls: ['./io-input.component.scss'],
  encapsulation : ViewEncapsulation.None,
  providers: [
    // ... some services here
  ]
})
export class IoInputComponent implements AfterViewInit, OnDestroy, MatFormFieldControl<any>, ControlValueAccessor {
  
  @Input() inputConfig : InputConfigInterface;
  private componentRef : any;
  // ... more variables ...
  
  constructor(
    private componentFactoryResolver: ComponentFactoryResolver
  ) {
  }
  ngAfterViewInit() {
    let comp : Promise<Type<DateComponent PlainTextComponent | ToggleComponent>>;
    switch(this.inputConfig.type){
      case "date":
        import('./date/date.component').then(m => {
            this.loadInput(m.DateComponent);                        
        });
      break;
      case "toggle":
        import('./toggle/toggle.component').then((m) => {
          this.loadInput(m.ToggleComponent);
        });
      break;
      // more cases for other dynamically imported components
      default:
        import('./plain-text/plain-text.component').then((m) => {
            this.loadInput(m.PlainTextComponent);
        });
      break;
    }
    debugger;
    const componentFactory = this.componentFactoryResolver.resolveComponentFactory(comp);
    this.componentRef = this.container.createComponent(componentFactory);
  }
  // What you get from the Promise is what you process here.
  // A ComponentType; hence the use of 'Type'
  loadInput(component : Type<DateComponent | ToggleComponent | PlainTextComponent>){
    const componentFactory = this.componentFactoryResolver.resolveComponentFactory<
      DateComponent |
      ToggleComponent |
      PlainTextComponent
    >(component);
    const viewContainerRef = this.ioInputView.viewContainerRef;
    viewContainerRef.clear();
    this.componentRef = viewContainerRef.createComponent<
      DateComponent |
      ToggleComponent |
      PlainTextComponent
    >(componentFactory);
    // As shown above, you just use the component itself as typing in createComponent and resolveComponentFactory, as that is your end result: a component to be rendered!
}

最后,我让动态组件加载并运行。 对我来说有很多陷阱,所以请确保你不会陷入同样的​​陷阱。 看看我的外卖。

暂无
暂无

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

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