Angular2 动态组件

        在前端开发过程中,经常会遇到一种情况,根据不同的类型,需要加载不同的组件,一般的做法是通过ngswitch语法来进行类型判断,在里面写组件。例如下面的代码,会根据不同的报表节点类型加载不同的component来绘制不同的图表
<div class="height100" [ngSwitch]="node.reportNodeType">
        <div class="height100" *ngSwitchCase="reportNodeType.handsonTable">
            <hotTable [data]="node.reportNodeValue.value?.data"
                      [columns]="node.reportNodeValue.value?.columns"
                      [colHeaders]="node.reportNodeValue.value?.colHeaders"
                      [options]="node.reportNodeValue.value?.options">
            </hotTable>
        </div>
        <div class="height100" *ngSwitchCase="reportNodeType.bar">
            <bi-drill-down-charts #biChart [node]="node" [nodeValue]="node.reportNodeValue" class="height100"
                                  [loading]="node.loading">
            </bi-drill-down-charts>
        </div>
        <div class="height100" *ngSwitchCase="reportNodeType.pie">
            <bi-drill-down-charts #biChart [node]="node" class="height100"
                                  [loading]="node.loading">
            </bi-drill-down-charts>
        </div>
        <div class="height100" *ngSwitchDefault>
            <bi-charts #biChart [node]="node" class="height100"
                       [loading]="node.loading"></bi-charts>
        </div>
    </div>
伴随的问题是随着图表类型的增加,这里面的ngSwitchCase语句也会相应增加,变得难以维护;最好的方式是动态的加载需要的组件信息;
    具体的做法:

1>report-node.component.html,report-node.component.ts来封装报表节点,对外所有使用报表节点的地方,直接引入ReportNodeComponent组件即可,这样,外界不需要关心到底使用哪种组件,以及如何显示的内部细节;report-node.component.html内容

<div #container></div>

report-node.component内容

@Component({
    selector: 'bi-report-node',
    templateUrl: './report-node.component.html',
    styles: []
})
export class ReportNodeComponent implements OnInit, OnDestroy {
    @Input() node: ReportNode;
    @ViewChild('container', {read: ViewContainerRef})
    container: ViewContainerRef;
    private componentRef: ComponentRef<{}>;

    constructor(private componentFactoryResolver: ComponentFactoryResolver,
                private reportNodeTypeService: ReportNodeTypeService) {

    }

    ngOnInit() {
        let type = this.node.reportNodeType;
        let component = this.reportNodeTypeService.getComponentType(type);
        let factory = this.componentFactoryResolver.resolveComponentFactory(component);
        this.componentRef = this.container.createComponent(factory);
        let instance = <AbstractCommonNodeComponent>this.componentRef.instance;
        instance.node = this.node;
    }


    ngOnDestroy(): void {
        if (this.componentRef) {
            this.componentRef.destroy();
            this.componentRef = null;
        }
    }

    resize(event: INgWidgetEvent) {
        if (this.componentRef) {
            let nodeComponent = <AbstractCommonNodeComponent>this.componentRef.instance;
            nodeComponent.resize(event);
        }
    }

组件内容中只有一个container标识,通过init方法根据节点类型动态创建组件,插入到container标识div中;

@ViewChild

Angular提供了一种称为DOM查询的机制。它以@ViewChild和@ViewChildren装饰器的形式出现.它们的行为相同,只有前者返回一个引用,而后者返回多个引用作为 QueryList 对象;通常这些装饰器和模板引用变量一起工作。模板引用变量可以理解为Dom元素的引用,可以将它看成html元素id类似的东西。这里通过ViewContainerRef的createComponent方法,在指定dom元素内插入组件;ViewContainerRef类的createComponent方法

abstract createComponent<C>(componentFactory: ComponentFactory<C>, index?: number, injector?: Injector, projectableNodes?: any[][], ngModule?: NgModuleRef<any>): ComponentRef<C>;

第一个参数要求插入组件的componentFactory对象,这里通过

let factory = this.componentFactoryResolver.resolveComponentFactory(component);

来获取;这里的component对象我们通过一个服务来获取,代码如下

import {Injectable} from '@angular/core';
import {ChartsComponent} from '../../charts/charts.component';
import {DrillDownChartComponent} from '../../charts/drill-down-chart/drill-down-chart.component';
import {HandsonTableNodeComponent} from './handson-table-node/handson-table-node.component';
import {QueryNodeViewComponent} from './query-node-view/query-node-view.component';

@Injectable()
export class ReportNodeTypeService {

    private mappings = {
        'bar': DrillDownChartComponent,
        'pie': DrillDownChartComponent,
        'handsonTable': HandsonTableNodeComponent,
        "query": QueryNodeViewComponent
    };

    constructor() {
    }

    getComponentType(typeName: string) {
        let type = this.mappings[typeName];
        if (!type) {
            type = ChartsComponent;
        }
        return type;
    }

}

定义了一个mapping,然后根据类型找到对应的component;

2>第二个问题,如何向动态生成的component对象中传递参数以及交互行为?

abstract createComponent<C>(componentFactory: ComponentFactory<C>, index?: number, injector?: Injector, projectableNodes?: any[][], ngModule?: NgModuleRef<any>): ComponentRef<C>;

方法的返回结果是一个component对象的引用,我们可以把动态component组件公用的参数抽象出来 为一个parentComponent,然后把componentRef 转换为这个parentComponent,将其对其赋值即可,比如这里我们将公用的属性和操作抽象成一个AbstractCommonNodeComponent,封装起来,如下所示,其他所有的动态生成的组件继承该组件

import {Component} from '@angular/core';
import {ReportNode} from '../report-node.model';
import {INgWidgetEvent} from 'ngx-draggable-widget-palan';


export abstract class AbstractCommonNodeComponent {
    node: ReportNode;

    constructor() {
    }

    abstract resize(event: INgWidgetEvent);
}

比如ChartsComponent,

import {Component, Input, OnInit} from '@angular/core';
import {DataMartService} from '../entities/data-mart/data-mart.service';
import {NzNotificationService} from 'ng-zorro-antd';
import {ReportNodeService} from '../entities/report-node/report-node.service';
import {AbstractCommonNodeComponent} from '../entities/report-node/abstract-common-node/abstract-common-node.component';

@Component({
    selector: 'bi-charts',
    templateUrl: './charts.component.html',
    styleUrls: [
        'charts.scss'
    ]
})
export class ChartsComponent extends AbstractCommonNodeComponent implements OnInit {
    echartsIntance: any;
    constructor(public reportNodeService: ReportNodeService,
                public notification: NzNotificationService,
                public dataMartService: DataMartService) {
        super();
    }

    ngOnInit(): void {
        console.log(this.node);
    }

    private triggerChartSize() {
        if (this.echartsIntance) {
            this.echartsIntance.resize();
        }
    }

    resize() {
        setTimeout(() => {
            this.triggerChartSize();
        }, 0);
    }

    onChartInit(ec) {
        this.echartsIntance = ec;
    }

}
最后我们做了类型强制转换,然后对其进行赋值操作
let instance = <AbstractCommonNodeComponent>this.componentRef.instance;
        instance.node = this.node;


总结:这里用到了抽象工厂设计模式,把所有的动态组件的具体细节封装起来,对外的组件是ReportNodeComponent,外部不需要知道具体的某种报表节点的具体显示方式细节;给定一种报表的类型,该组件就通过对应的service(这里是reportNodeTypeService)来找到对应的组件并渲染出来;之后扩展报表节点的时候,只需要在reportNodeTypeService中把新的组件加进去就可以。

另外,这些动态组件要在module的entryComponents: [中声明


  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值