简体   繁体   English

在从外部JSON文件构造的模板中使用延迟加载的Angular 2组件

[英]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 ComponentsModuleDynamicComponentService导入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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM