动态组件加载器 - 解析组件的共享服务
Dynamic component loader - shared service for the resolving components
我必须使用 ComponentFactoryResolver 来动态添加组件。
对于我的示例,我有一个模态 window ,我在按下视图上的某个按钮后动态加载它。
此操作的代码如下:
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();
});
每次我想使用这个模态时window我需要输入相似的代码,只是组件名称不同。
我想为此创建一个共享服务,但我找不到适合我的情况的好的解决方案我的意思是动态加载的组件。
知道如何使用此代码创建良好的服务吗?
由于您将动态加载组件,因此您必须在某个地方注册这些组件,那就是在模态组件装饰器的 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 classes是函数的糖语法,所有非匿名函数都有一个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
class:
的定义
export class DynamicComponent {
constructor(public component: Type<any>, data: any) {}
}
之所以创建这个助手class,是因为resolveComponentFactory
需要一个组件参数Type<T>
,而dynamicComponents.find()
的结果是联合类型,所以如果我们不希望 typescript 编译器报错,我们应该 patch 这个 class.
除 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 编辑:
显然,访问 Function
对象的 name
属性 并不真正适用于生产(你会得到一个 no component factory found 错误),因为在 uglyfying 你代码,函数的名称被破坏并且不匹配。 angular repo 上有一条问题评论,解释了此问题的一些解决方法。
我必须使用 ComponentFactoryResolver 来动态添加组件。 对于我的示例,我有一个模态 window ,我在按下视图上的某个按钮后动态加载它。 此操作的代码如下:
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();
});
每次我想使用这个模态时window我需要输入相似的代码,只是组件名称不同。
我想为此创建一个共享服务,但我找不到适合我的情况的好的解决方案我的意思是动态加载的组件。
知道如何使用此代码创建良好的服务吗?
由于您将动态加载组件,因此您必须在某个地方注册这些组件,那就是在模态组件装饰器的 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 classes是函数的糖语法,所有非匿名函数都有一个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
class:
export class DynamicComponent {
constructor(public component: Type<any>, data: any) {}
}
之所以创建这个助手class,是因为resolveComponentFactory
需要一个组件参数Type<T>
,而dynamicComponents.find()
的结果是联合类型,所以如果我们不希望 typescript 编译器报错,我们应该 patch 这个 class.
除 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 编辑:
显然,访问 Function
对象的 name
属性 并不真正适用于生产(你会得到一个 no component factory found 错误),因为在 uglyfying 你代码,函数的名称被破坏并且不匹配。 angular repo 上有一条问题评论,解释了此问题的一些解决方法。