首页 文章

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

提问于
浏览
0

我正在构建一个角度组件doc / demo站点,它列举了一个Angular组件列表和一组带有使用细节和示例的JSON文档 .

这是一个基本组件的示例:

import { Component } from '@angular/core';

@Component({
    selector: 'my-button',
    template: `<button class="btn btn-default">I am a banana</button>`
})
export class Button {}

首先,我使用预构建脚本自动生成如下所示的 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];

然后我将其加载到 app.module.ts (为简洁起见,删除了其他导入):

import { COMPONENT_ROUTES, COMPONENT_DECLARATIONS } from './componentLoader';
// ...
@NgModule({
    ...
    declarations: [ ... ].concat(COMPONENT_DECLARATIONS)

这很好用;我可以在视图组件中使用该按钮:

import { Component } from '@angular/core';

@Component({
    selector: 'button-test',
    template: `
    <my-button></my-button>
`
})
export class ButtonTest {}

呜!

现在,我希望能够枚举JSON文档的文件夹,以查找与此组件及其使用方式相关的详细信息 . JSON看起来像这样:

{
    "name": "button",
    "description": "An example of an 'I am a banana' button",
    "exampleHTML": "<div><my-button></my-button></div>"
}

请注意 exampleHTML 中我是如何尝试使用 componentLoader 中加载的组件 .

为了生成文档视图,我构建了一个docs服务,该服务采用 id param并在/ 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;
    }   
}

然后,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:

也许是因为这些组件的加载方式,或者因为我正确的模板引擎,或者因为在构建链的中间运行了一个黑盒Webpack服务,应该显示实际 button 组件的模板完全是空白 .

在组件详细信息视图中,我可以手动将模板字符串设置为 <my-button></my-button> 并且它可以正常工作,但是当我尝试从JSON doc加载它时它不会 .

How can I get the detail component to actually render the Button component used in the JSON doc?

1 回答

  • 2

    See working Plnkr Here

    您可以通过创建动态组件来解决问题,而不是使用 [innerHTML] 指令 .

    为此,您需要使用Compiler.compileModuleAndAllComponentsAsync方法动态加载模块及其组件 .

    首先,您需要创建一个模块,其中包含您希望能够在动态HTML中使用的应用程序组件 .

    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 {}
    

    创建将用于生成动态模块和组件的 dynamicComponent.service.ts . 在动态加载的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;
      }
    
    }
    

    ComponentsModuleDynamicComponentService 导入 app.module.ts

    @NgModule({
        ...
        imports: [        
            ...
            ComponentsModule
        ],
        providers: [
            ...
            DynamicComponentService
        ]
        ...
    })
    export class AppModule {}
    

    最后, ComponentDetail 被更改为使用新的 DynamicComponentService 来获取动态组件的 ComponentFactory .

    获得新的 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();
      }
    }
    

相关问题