[英]Consuming lazy-loaded Angular 2 components in a template constructed from an external JSON file
I'm building an angular components doc/demo site that enumerates a list of Angular components and a set of JSON documents with usage details and examples. 我正在构建一个angular组件doc / demo网站,该站点枚举了Angular组件的列表以及一组JSON文档以及用法细节和示例。
Here's an example of a basic component: 这是一个基本组件的示例:
import { Component } from '@angular/core';
@Component({
selector: 'my-button',
template: `<button class="btn btn-default">I am a banana</button>`
})
export class Button {}
To start with, I'm using a pre-build script to autogenerate a componentLoader
file that looks like this: 首先,我使用一个预构建脚本来自动生成如下所示的
componentLoader
文件:
import { Route } from '@angular/router';
import { Button } from '../app/components/elements/button/button';
export const COMPONENT_ROUTES: Route[] = [{ path: 'button', component: Button }];
export const COMPONENT_DECLARATIONS: Array<any|any[]> = [Button];
I then load that into app.module.ts
(removed other imports for brevity): 然后,将其加载到
app.module.ts
(为简便起见,删除了其他导入):
import { COMPONENT_ROUTES, COMPONENT_DECLARATIONS } from './componentLoader';
// ...
@NgModule({
...
declarations: [ ... ].concat(COMPONENT_DECLARATIONS)
This works fine; 这很好用; I can use the button in a view component:
我可以在视图组件中使用按钮:
import { Component } from '@angular/core';
@Component({
selector: 'button-test',
template: `
<my-button></my-button>
`
})
export class ButtonTest {}
Woo! !
Now, I want need be able to enumerate a folder of JSON documents to look for details related to this component and how it's used. 现在,我希望能够枚举一个JSON文档文件夹,以查找与此组件及其使用方式相关的详细信息。 The JSON looks like this:
JSON如下所示:
{
"name": "button",
"description": "An example of an 'I am a banana' button",
"exampleHTML": "<div><my-button></my-button></div>"
}
Notice how in exampleHTML
I'm trying to use the component we loaded in the componentLoader
. 注意在
exampleHTML
我如何尝试使用我们在componentLoader
加载的componentLoader
。
To generate doc views, I built a docs service that takes an id
param and searches for a matching JSON document in the /docs dir: 为了生成文档视图,我构建了一个docs服务,该服务采用
id
参数,并在/ docs目录中搜索匹配的JSON文档:
import {Injectable} from '@angular/core';
declare const require: any;
@Injectable()
export class DocsService {
constructor() { }
getDoc(id: string) {
const json: string = require('../docs/' + id + '.json');
return json;
}
}
Then the detail component imports the service and consumes the JSON doc component in the template: 然后,detail组件导入服务并使用模板中的JSON doc组件:
import { Component } from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router';
import { DocsService } from '../../services/docs.service';
declare const require: any;
const template: string = require('./componentDetail.html');
@Component({
selector: 'componentDetail',
template: `<div>Name: {{componentDoc.name}}</div>
Examples:<br><br><div [innerHTML]="componentExample"></div>`
})
export class ComponentDetail {
public componentDoc: any
private _componentExample: string
public get componentExample(): SafeHtml {
return this._sanitizer.bypassSecurityTrustHtml(this._componentExample);
}
constructor(
private route: ActivatedRoute,
private docsService: DocsService,
private _sanitizer: DomSanitizer
) {
this.route.params.forEach((params: Params) => {
if (params['id'] !== undefined) {
let id = params['id'];
this.componentDoc = this.docsService.getDoc(id)
this._componentExample = this.componentDoc.exampleHTML
} else { throw Error('Not found') }
});
}
}
The problem: 问题:
Perhaps because of the way these components are loaded, or because I'm not loading HTML into Angular's templating engine correctly, or because there's a black-box Webpack service that runs in the middle of the build chain, the template that should show the actual button
component, is completely blank. 可能是因为这些组件的加载方式,或者是因为我没有将HTML正确地加载到Angular的模板引擎中,或者是因为在构建链的中间运行了一个黑盒Webpack服务,该模板应该显示实际的
button
组件,完全空白。
In the component detail view, I can manually set the template string to <my-button></my-button>
and it works, but it doesn't when I try to load it from the JSON doc. 在组件详细信息视图中,我可以手动将模板字符串设置为
<my-button></my-button>
并且它可以工作,但是当我尝试从JSON文档中加载它时却不能。
How can I get the detail component to actually render the Button component used in the JSON doc? 如何获取详细信息组件以实际呈现JSON文档中使用的Button 组件 ?
See working Plnkr Here 在这里查看工作的Plnkr
Instead of using the [innerHTML]
directive you can instead attack the problem by creating dynamic components. 除了使用
[innerHTML]
指令,您还可以通过创建动态组件来解决该问题。
To do so you will need to use the Compiler.compileModuleAndAllComponentsAsync method to dynamically load a Module and its components. 为此,您将需要使用Compiler.compileModuleAndAllComponentsAsync方法来动态加载模块及其组件。
First you will need to create a module that contains your applications components that you want to be able to use within the dynamic HTML. 首先,您将需要创建一个模块,其中包含要在动态HTML中使用的应用程序组件。
Change your componentLoader
to be a module like so 将您的
componentLoader
更改为像这样的模块
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { Button } from '../../app/components/elements/button/button';
const COMPONENT_DECLARATIONS: Array<any|any[]> = [
Button
];
@NgModule({
declarations: COMPONENT_DECLARATIONS,
exports: COMPONENT_DECLARATIONS,
imports: [CommonModule]
})
export class ComponentsModule {}
Create dynamicComponent.service.ts
that will be used to generate the dynamic modules and components. 创建用于生成动态模块和组件的
dynamicComponent.service.ts
。 The key to using components within the dynamically loaded HTML is how we will import the ComponentsModule
within DynamicComponentModule
. 在动态加载的HTML中使用组件的关键是如何在
DynamicComponentModule
导入ComponentsModule
。
import {
Component,
Compiler,
ModuleWithComponentFactories,
ComponentFactory,
NgModule,
Type
} from '@angular/core';
import { Injectable } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ComponentsModule } from '../components/components.module';
@Injectable()
export class DynamicComponentService {
private componentFactoriesCache: ComponentFactory<any>[] = [];
constructor(private compiler: Compiler) { }
public getComponentFactory(templateHtml: string): Promise<ComponentFactory<any>> {
return new Promise<ComponentFactory<any>>((resolve) => {
let factory = this.componentFactoriesCache[templateHtml];
if ( factory ) {
resolve(factory);
} else {
let dynamicComponentType = this.createDynamicComponent(templateHtml);
let dynamicModule = this.createDynamicModule(dynamicComponentType);
this.compiler.compileModuleAndAllComponentsAsync(dynamicModule)
.then((mwcf: ModuleWithComponentFactories<any>) => {
factory = mwcf.componentFactories.find(cf => cf.componentType === dynamicComponentType);
this.componentFactoriesCache[templateHtml] = factory;
resolve(factory);
});
}
});
}
private createDynamicComponent(templateHtml: string): Type<NgModule> {
@Component({
template: templateHtml,
})
class DynamicDocComponent {}
return DynamicDocComponent;
}
private createDynamicModule(dynamicComponentType: Type<Component>) {
@NgModule({
declarations: [dynamicComponentType],
imports: [CommonModule, ComponentsModule]
})
class DynamicDocComponentModule {}
return DynamicDocComponentModule;
}
}
Import ComponentsModule
and DynamicComponentService
into app.module.ts
将
ComponentsModule
和DynamicComponentService
导入app.module.ts
@NgModule({
...
imports: [
...
ComponentsModule
],
providers: [
...
DynamicComponentService
]
...
})
export class AppModule {}
Finally the ComponentDetail
is changed to use the new DynamicComponentService
to get the ComponentFactory
for the dynamic component. 最后,将
ComponentDetail
更改为使用新的DynamicComponentService
来获取动态组件的ComponentFactory
。
Once you have the new ComponentFactory
you can then use a view container to create and populate the component with the dynamic html. 一旦有了新的
ComponentFactory
,就可以使用视图容器创建动态html并填充该组件。
import {
Component,
ComponentRef,
ViewChild,
ViewContainerRef,
AfterContentInit,
OnDestroy
} from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router';
import { DocsService } from './services/docs.service';
import { DynamicComponentService } from './services/dynamicComponent.service';
@Component({
selector: 'componentDetail',
template: `<div>Name: {{componentDoc.name}}</div>
Examples:<br><br><template #container></template>
`
})
export class ComponentDetail implements AfterContentInit, OnDestroy {
private componentRouteID: string;
private componentDoc: any;
private componentExampleHtml: string;
private componentRef: ComponentRef<any>;
@ViewChild('container', { read: ViewContainerRef })
private container: ViewContainerRef;
constructor(private route: ActivatedRoute,
private docsService: DocsService,
private dynamicComponentService: DynamicComponentService) {
this.route.params.forEach((params: Params) => {
this.componentRouteID = params['id'];
if (this.componentRouteID ) {
this.componentDoc = this.docsService.getDoc(this.componentRouteID);
this.componentExampleHtml = this.componentDoc.exampleHTML;
} else {
throw Error('Not found');
}
});
}
public ngAfterContentInit() {
this.dynamicComponentService.getComponentFactory(this.componentExampleHtml).then((factory) => {
this.componentRef = this.container.createComponent(factory);
});
}
public ngOnDestroy() {
this.componentRef.destroy();
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.