<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: [中声明