在 angular-2 中动态创建延迟加载组件
Dynamically creating lazy loaded components in angular-2
我们在 cms (Sitecore) 中托管一个 angular2 应用程序,我们要求内容编辑者可以在页面中添加、删除和重新排序我们应用程序的组件,他们还可以根据需要添加其他组件。
我们通过让 cms 生成脚本标签来加载我们的组件来做到这一点。生成的 html 的示例是:
<body>
<sales-root></sales-root>
<script type="text/html" id="generated-header">
<sales-welcome></sales-welcome>
</script>
<script type="text/html" id="generated-content">
<sales-personal-info></sales-personal-info>
<div><!-- regular html content --></div>
</script>
</body>
在 sales-root
组件中我们有
export class AppComponent extends Translation implements OnInit {
@ViewChild('header', { read: ViewContainerRef }) headerRef;
@ViewChild('content', { read: ViewContainerRef }) contentRef;
ngOnInit() {
this.loadSitecorePlaceholders(this.headerRef, 'generated-header');
this.loadSitecorePlaceholders(this.contentRef, 'generated-content');
// ...
}
private loadSitecorePlaceholders(containerRef: ViewContainerRef, placeholder: string) {
// get the generated components listed from the sitecore placeholder
const generatedContent = this.elementRef.nativeElement.parentNode.children[placeholder];
if (generatedContent === undefined) { return; }
if (containerRef === undefined) { return; }
this.createComponentService.createComponentsFromHtml(containerRef, generatedContent.innerText);
// we've finished creating all the components we need, remove the nodes created by sitecore.
this.elementRef.nativeElement.parentNode.removeChild(generatedContent);
}
}
在 CreateComponentService
中,我们初始化了一组允许的组件工厂,以及一个通用的 html 主机组件工厂:
this._selectableFactories = creatableComponents
.map((component: Type<any>) =>
this.componentFactoryResolver.resolveComponentFactory(component));
我们解析 CMS 为选择器生成的脚本标记,如果找到它们,我们会添加组件,或者我们注入通用 html 组件:
private createAngularComponent(
container: ViewContainerRef,
factory: ComponentFactory<Type<any>>,
element: HTMLElement,
) {
const selector = factory.selector.toLocaleLowerCase();
container.createComponent(factory);
// ...
}
private createHtmlComponent(container: ViewContainerRef, element: HTMLElement) {
const component = container.createComponent(this.htmlHostFactory).instance;
component.content = element.outerHTML;
}
除性能外,所有这些都运行良好。我们有很多组件,加载和编译它们都需要一段时间(在快速机器上需要 3 到 4 秒)。我们通过两种方式解决这个问题:
- 转向 AOT 而不是 JIT。证明由于内部库存在问题,但我们正在解决它。
- 根据需要延迟加载组件。我们只需要任何页面上的组件子集,因此这应该会给我们带来一些收益。
对于 #2,我们需要在 @NgModule
中列出 entryComponents
中的所有可创建组件,因此它们必须在生成时都存在...对吗?我还预加载了工厂,这样我就可以找到选择器,但我可以在开发中构建该列表。
理想情况下我有一个选择器字典到惰性组件工厂工厂,第一次调用会下载组件并加载它然后注入它,这会发生 post ngInit 并且还会给出一个外观加载速度更快。
是否可以延迟加载组件?到目前为止,我看到的所有示例都使用了路由器,而我们并不是因为动态组合。
怎么样?
你有几个选择
1) 将延迟加载的组件添加到它们自己的模块中,并按照Here is what you need to know about dynamic components in Angular中的描述按需加载和编译这些组件。您将需要使用 compileModuleAndAllComponentsAsync
方法。这是示例:
ngAfterViewInit() {
System.import('app/t.module').then((module) => {
_compiler.compileModuleAndAllComponentsAsync(module.TModule)
.then((compiled) => {
const m = compiled.ngModuleFactory.create(this._injector);
const factory = compiled.componentFactories[0];
const cmp = factory.create(this._injector, [], null, m);
})
})
}
2) 另一种方法是 AOT 编译组件并按需加载它们的工厂。 Angular AOT 编译器将 AOT 输出生成为有效的 ES6/TS 模块,因此您可以单独导入组件并直接从工厂创建它们的实例:
System.import('components/ex.component.ngfactory').then((factory) => {
const cmp = factory.create(this._injector, [], null, m);
})
或者您可以获得包含所有组件的模块工厂并创建它的实例。
我们在 cms (Sitecore) 中托管一个 angular2 应用程序,我们要求内容编辑者可以在页面中添加、删除和重新排序我们应用程序的组件,他们还可以根据需要添加其他组件。
我们通过让 cms 生成脚本标签来加载我们的组件来做到这一点。生成的 html 的示例是:
<body>
<sales-root></sales-root>
<script type="text/html" id="generated-header">
<sales-welcome></sales-welcome>
</script>
<script type="text/html" id="generated-content">
<sales-personal-info></sales-personal-info>
<div><!-- regular html content --></div>
</script>
</body>
在 sales-root
组件中我们有
export class AppComponent extends Translation implements OnInit {
@ViewChild('header', { read: ViewContainerRef }) headerRef;
@ViewChild('content', { read: ViewContainerRef }) contentRef;
ngOnInit() {
this.loadSitecorePlaceholders(this.headerRef, 'generated-header');
this.loadSitecorePlaceholders(this.contentRef, 'generated-content');
// ...
}
private loadSitecorePlaceholders(containerRef: ViewContainerRef, placeholder: string) {
// get the generated components listed from the sitecore placeholder
const generatedContent = this.elementRef.nativeElement.parentNode.children[placeholder];
if (generatedContent === undefined) { return; }
if (containerRef === undefined) { return; }
this.createComponentService.createComponentsFromHtml(containerRef, generatedContent.innerText);
// we've finished creating all the components we need, remove the nodes created by sitecore.
this.elementRef.nativeElement.parentNode.removeChild(generatedContent);
}
}
在 CreateComponentService
中,我们初始化了一组允许的组件工厂,以及一个通用的 html 主机组件工厂:
this._selectableFactories = creatableComponents
.map((component: Type<any>) =>
this.componentFactoryResolver.resolveComponentFactory(component));
我们解析 CMS 为选择器生成的脚本标记,如果找到它们,我们会添加组件,或者我们注入通用 html 组件:
private createAngularComponent(
container: ViewContainerRef,
factory: ComponentFactory<Type<any>>,
element: HTMLElement,
) {
const selector = factory.selector.toLocaleLowerCase();
container.createComponent(factory);
// ...
}
private createHtmlComponent(container: ViewContainerRef, element: HTMLElement) {
const component = container.createComponent(this.htmlHostFactory).instance;
component.content = element.outerHTML;
}
除性能外,所有这些都运行良好。我们有很多组件,加载和编译它们都需要一段时间(在快速机器上需要 3 到 4 秒)。我们通过两种方式解决这个问题:
- 转向 AOT 而不是 JIT。证明由于内部库存在问题,但我们正在解决它。
- 根据需要延迟加载组件。我们只需要任何页面上的组件子集,因此这应该会给我们带来一些收益。
对于 #2,我们需要在 @NgModule
中列出 entryComponents
中的所有可创建组件,因此它们必须在生成时都存在...对吗?我还预加载了工厂,这样我就可以找到选择器,但我可以在开发中构建该列表。
理想情况下我有一个选择器字典到惰性组件工厂工厂,第一次调用会下载组件并加载它然后注入它,这会发生 post ngInit 并且还会给出一个外观加载速度更快。
是否可以延迟加载组件?到目前为止,我看到的所有示例都使用了路由器,而我们并不是因为动态组合。 怎么样?
你有几个选择
1) 将延迟加载的组件添加到它们自己的模块中,并按照Here is what you need to know about dynamic components in Angular中的描述按需加载和编译这些组件。您将需要使用 compileModuleAndAllComponentsAsync
方法。这是示例:
ngAfterViewInit() {
System.import('app/t.module').then((module) => {
_compiler.compileModuleAndAllComponentsAsync(module.TModule)
.then((compiled) => {
const m = compiled.ngModuleFactory.create(this._injector);
const factory = compiled.componentFactories[0];
const cmp = factory.create(this._injector, [], null, m);
})
})
}
2) 另一种方法是 AOT 编译组件并按需加载它们的工厂。 Angular AOT 编译器将 AOT 输出生成为有效的 ES6/TS 模块,因此您可以单独导入组件并直接从工厂创建它们的实例:
System.import('components/ex.component.ngfactory').then((factory) => {
const cmp = factory.create(this._injector, [], null, m);
})
或者您可以获得包含所有组件的模块工厂并创建它的实例。