由于您将动态加载组件,因此您必须在某处注册这些组件,即在您的模态组件装饰器的entryComponents 内。此外,由于这些组件是打字稿classes,因此您需要从您调用模态的任何组件中导入它们。为了在一个地方处理这个问题,我们将把它们导入到一个文件中,然后将它们导出到一个数组中。
所以在这里你将保留所有可能的动态组件,让我们将此文件称为dynamic-components.ts:
import { FooComponent } from './path/to/foo';
import { BarComponent } from './path/to/bar';
import { BazComponent } from './path/to/baz';
// export all dynamic components
export const dynamicComponents = [
FooComponent, BarComponent, BazComponent
]
然后在你的模态组件中,你可以将这些组件分散到entryComponents属性中
import { dynamicComponents } from './path/to/dynamic-components';
@Component({
//...
entryComponents: [ ...dynamicComponents ]
})
export class ModalComponent {}
到目前为止,您应该都知道这些。
现在,在您的模态组件中,您可以创建一个方法,该方法将呈现组件,将组件名称和一些元数据作为参数来动态处理道具,道具我的意思是@Input() 和@Output() 装饰属性。这将使您的模态更加灵活,因为您将能够渲染具有不同输入和输出的组件。
因此,您不必像现在这样在方法中硬编码组件,而是必须从 dynamicComponents 数组中提取它。
由于 Javascript 类是函数的糖语法,所有非匿名函数都有一个 name 属性,这样您就可以将函数提供的名称参数与 dynamicComponents 中的组件名称相匹配。
export class ModalComponent {
//...
createComponent(name: string, metadata: any) {
const viewContainerRef = this.entryContainer.viewContainerRef;
const cmpClass = dynamicComponents.find(cmp => cmp.name === name);
const cmpToCreate = new DynamicComponent(cmpClass, metadata);
const componentFactory = this.cmpFactoryResolver.resolveComponentFactory(cmpToCreate.component)
viewContainerRef.clear();
const cmpRef = viewContainerRef.createComponent(componentFactory);
// patch input values ...
for ( let input in metadata.inputs ) {
if ( metadata.inputs.hasOwnProperty(input) ) {
cmpRef.instance[input] = metadata.inputs[input];
}
}
// and subscribe to outputs
for ( let output in metadata.outputs ) {
if ( metadata.outputs.hasOwnProperty(output) ) {
console.log('hasOuput', metadata.outputs[output]);
cmpRef.instance[output].subscribe(metadata.outputs[output]);
}
}
}
}
有几件事要提一下。这是DynamicComponent 类的定义:
export class DynamicComponent {
constructor(public component: Type<any>, data: any) {}
}
创建这个帮助类的原因,是因为resolveComponentFactory 期待一个带有Type<T> 的组件参数,而dynamicComponents.find() 的结果是一个联合类型,所以如果我们不希望打字稿编译器抱怨,我们应该修补这个类。
除了metadata 参数外,其余的功能几乎就是你所拥有的。现在,如果您在模态中实例化的组件具有输入和输出,除非您专门设计这些组件以满足某些标准,否则可能具有不同的输入和输出。这就是元数据参数,只是一个具有输入和输出的对象。我想当您实际调用该方法时会更清楚,如下所示:
export class SomeComponentThatRendersTheModal() {
renderFooComponent() {
// I don't know how you call your modal, so I'll just assume there's a modal service or whatever
this.modalService.openModal();
this.modalService.createComponent(
'FooComponent', {
inputs : { fooInputTest : 'kitten' },
outputs : { fooOutputTest : handleOutput }
}
);
}
// You can pass this method as the subscription `next` handler
handleOutput(emittedEvent) {
// ...
}
}
FooComponent 是这样的:
@Component({
selector: 'foo',
template: `
<h1>Foo Component, here's the input: " {{ fooInputTest }} "</h1>
<button (click)="fooOutputTest.emit('Greetings from foo')">Foo output test</button>
`
})
export class FooComponent {
@Input()
fooInputTest: any;
@Output()
fooOutputTest: EventEmitter<any> = new EventEmitter<any>();
}
现在,您当然可以更改 metadata 的外观或处理修补输入值的方式或作为处理程序传递给输出的内容,但这是动态创建不同组件的基本基础.
当然我也设置了demo。希望对您有所帮助。
12/14/07 编辑:
显然访问Function 对象的name 属性在生产环境中并不能真正起作用(你会得到一个没有找到组件工厂的错误),因为在丑化你的代码之后,函数的名称会被破坏并获胜不匹配。 angular repo 上有一个问题评论,解释了一些解决此问题的方法。