[英]Trying to convert a Component name from string to Component type in angular 9 gives Type passed in is not ComponentType error
[英]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
所有组件,则一切正常。 我将ComponentFactoryResolver
与resolveComponentFactory
,并根据我通过@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"
的属性。 我在动态导入组件的方法中做错了什么吗?
正如@MikeOne 所暗示的那样,Nethanel Basal 的文章提供了一个很好的方向。 我强烈建议任何使用动态组件加载的人阅读它。 这里有一些链接。
因为在我的情况下,我更多地以参数化的方式工作,所以 Nethanel 文章中的基本示例还不够模块化。
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.