我必须使用ComponentFactoryResolver动态添加组件。 对于我的例子,我有一个模态窗口,我在按下视图上的某个按钮后动态加载。 这个动作的代码如下:
let componentFactory = this.componentFactoryResolver.resolveComponentFactory(XYZComponent);
let viewContainerRef = this.containerHost.viewContainerRef;
viewContainerRef.clear();
let componentRef = viewContainerRef.createComponent(componentFactory);
let _XYZComponent = componentRef.instance as XYZComponent;
_XYZComponent.close.subscribe(() => {
componentRef.destroy();
});
每次当我想使用这个模态窗口时,我都需要输入相似的代码,只有不同的组件名称。
我想为此创建一个共享服务,但我无法为我的案例找到一个好的解决方案,我的意思是动态加载的组件。
知道如何创建一个好的服务来使用这段代码吗?
答案 0 :(得分:1)
由于您将动态加载组件,因此您必须在某处注册这些组件,并且它位于模态组件装饰器的entryComponents
内。此外,由于这些组件是打字稿class
es,因此您需要从您调用模式的任何组件中导入它们。为了在一个地方处理这个问题,我们将这些导入到一个文件中,然后将它们导出到一个数组中。
所以在这里你将保留所有可能的动态组件,让我们调用这个文件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
属性,这样您就可以将函数提供的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()
的结果是联合类型,所以如果我们没有&#39 ;;想要打字稿编译器抱怨,我们应该修补这个类。
除了metadata
参数之外,函数的其余部分几乎就是你所拥有的。现在,如果您在模态中实例化的组件具有输入和输出,除非您专门设计这些组件以满足某些条件,否则可能具有不同的输入和输出。这就是metada参数是什么,只是一个带有输入和输出的对象。我猜你实际调用这个方法时会更清楚,比如:
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编辑:
显然,访问name
对象的Function
属性并不能真正用于生产(你会发现没有组件工厂发现错误),因为在对代码进行了丑化之后,函数的名称被破坏并且不匹配。 angular repo上有一个问题评论,解释了此问题的一些解决方法。