Angular 18独立组件中ECharts控制台警告「已有图表实例初始化」的问题求助
Angular 18独立组件中ECharts控制台警告「已有图表实例初始化」的问题求助
大家好,我在开发Angular v18的Web应用时遇到了个棘手的问题,想请各位帮忙排查下:
我的应用里有多个仪表盘类的独立组件,里面用到了Apache ECharts的堆叠折线图、柱状图、仪表盘等多种图表。但每次在页面间导航的时候,控制台都会弹出警告:
[ECharts] There is a chart instance already initialized on the dom.
我尝试过在组件里用dispose方法来清理旧的图表实例,但当从其他页面返回原组件页面时,这个警告还是会出现,感觉之前的实例好像没被彻底清理干净。
下面是我的折线图组件代码,麻烦帮忙看看哪里处理得不对:
import { Component, ElementRef, Input, OnChanges, SimpleChanges, AfterViewInit, OnInit, Output, EventEmitter, OnDestroy } from '@angular/core'; import { EChartsOption } from 'echarts'; import { NgxEchartsModule } from 'ngx-echarts'; import * as echarts from 'echarts'; import { NgxEchartsDirective, provideEcharts } from 'ngx-echarts'; import { ColorService } from '../../services/color.service'; import { CommonModule } from '@angular/common'; import { Series } from '../../services/models/lineChartTimeSeriesDTO'; @Component({ selector: 'app-line-chart', standalone: true, imports: [ NgxEchartsDirective, NgxEchartsModule, CommonModule ], providers: [provideEcharts()], templateUrl: './line-chart.component.html', styleUrl: './line-chart.component.css' }) export class LineChartComponent implements OnChanges, AfterViewInit, OnInit, OnDestroy { @Output() chartReady = new EventEmitter<echarts.ECharts>(); @Input() data: Series[] = [{ name: 'default', type: 'line', data: [], smooth: false, showSymbol: false, emphasis: { disabled: true } }]; @Input() labels: string[] = []; @Input() title: string = ""; @Input() unit: string = ""; @Input() seriesName: string = ""; @Input() legendData: string[] = []; @Input() chartHeight: number = 400; @Input() headerTitle: string = ""; @Input() multiplicativeFactor: number = 1; @Input() rotateLabelXAxis: number = 0; @Input() showSeconds: boolean = false; chartOptions: EChartsOption = {}; chartInstance: any = null; private resizeListener: () => void = () => {}; constructor(private el: ElementRef, private colorService: ColorService) { this.chartOptions = this.getChartOptions(); } ngOnInit(): void { } ngAfterViewInit() { const container = this.el.nativeElement.querySelector('.chart-container-line'); if (!container) { return; } // 先清理可能存在的旧实例 this.cleanupOldInstance(container); container.style.height = `${this.chartHeight}px`; container.style.width = '100%'; requestAnimationFrame(() => { this.initChart(container); this.chartReady.emit(this.chartInstance); }); } private initChart(container: HTMLElement) { // 再次检查并清理实例 this.cleanupOldInstance(container); this.chartInstance = echarts.init(container); this.chartInstance.setOption(this.chartOptions); this.chartInstance.on('brushEnd', (params: any) => this.onChartInit(params)); // 把resize事件的监听函数存起来,方便销毁时移除 this.resizeListener = () => { if (this.chartInstance) { this.chartInstance.resize(); } }; window.addEventListener('resize', this.resizeListener); } private cleanupOldInstance(container: HTMLElement) { const existingInstance = echarts.getInstanceByDom(container); if (existingInstance) { existingInstance.dispose(); container.removeAttribute('_echarts_instance_'); } } ngOnChanges(changes: SimpleChanges): void { /* refresh chart on data change */ if (changes['title'] || changes['unit'] || changes['data']) { this.updateChart(); } } onChartInit(params: any) { /* brush events */ if (params.areas && params.areas.length > 0) { const selectedArea = params.areas[0]; const coordRange = selectedArea.coordRange; if (coordRange) { const [start, end] = coordRange; /* Update x interval */ this.updateXAxisRange(start, end); /* Remove selection */ this.chartInstance.dispatchAction({ type: 'brush', areas: [] }); } } } private updateXAxisRange(start: number, end: number) { /* edit the visible range on x axis */ this.chartInstance.setOption({ xAxis: { min: start, max: end } }); } private getChartOptions(): EChartsOption { const maxValue = this.findFullScale(this.data); return { animation: false, title: { text: this.title, left: "center", textStyle: { color: this.colorService.getColorFromCSSVariable("--whiteStandard") }, }, legend: { data: this.legendData, type: 'scroll', textStyle: { color: this.colorService.getColorFromCSSVariable("--whiteStandard") }, pageIconColor: this.colorService.getColorFromCSSVariable("--whiteStandard"), pageIconInactiveColor: this.colorService.getColorFromCSSVariable("--lightGrey"), pageTextStyle: { color: this.colorService.getColorFromCSSVariable("--whiteStandard") } }, tooltip: { show: true, trigger: 'axis', formatter: (params: any) => { const timestamp = this.formatDate(params[0].axisValueLabel, this.showSeconds) let dataValues = params.map((item: any) => `${item.seriesName}: ${(item.value * this.multiplicativeFactor).toFixed(2)} ${this.unit}` ).join('<br/>'); return `${timestamp}<br/>${dataValues}`; } }, xAxis: { type: 'category', data: this.labels, axisLabel: { rotate: this.rotateLabelXAxis, color: this.colorService.getColorFromCSSVariable("--whiteStandard") }, axisLine: { lineStyle: { color: this.colorService.getColorFromCSSVariable("--lightGrey") } } }, yAxis: { type: 'value', axisLabel: { color: this.colorService.getColorFromCSSVariable("--whiteStandard"), formatter: (value: number) => `${(value * this.multiplicativeFactor).toFixed(2)} ${this.unit}` }, axisLine: { lineStyle: { color: this.colorService.getColorFromCSSVariable("--lightGrey") } }, splitLine: { lineStyle: { color: this.colorService.getColorFromCSSVariable("--darkGrey") } }, max: maxValue }, series: this.data.map(series => ({ ...series, lineStyle: { width: 2 }, areaStyle: {} })) }; } private findFullScale(data: Series[]): number { return data.length ? Math.max(...data.flatMap(s => s.data as number[])) * 1.1 : 100; } private updateChart() { this.chartOptions = this.getChartOptions(); if (this.chartInstance) { this.chartInstance.setOption(this.chartOptions); } } private formatDate(timestamp: string, showSeconds: boolean): string { const date = new Date(timestamp); return showSeconds ? date.toLocaleString() : `${date.toLocaleDateString()} ${date.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}`; } ngOnDestroy(): void { // 组件销毁时必须清理实例和事件监听 if (this.chartInstance) { this.chartInstance.dispose(); this.chartInstance = null; } window.removeEventListener('resize', this.resizeListener); } }
我自己调整了一些逻辑,比如把实例抽成单独的清理函数,还加了OnDestroy钩子来在组件销毁时彻底清理实例和resize事件监听,但还是没解决问题。现在的疑问是:是不是清理实例的时机不对?或者ngAfterViewInit里的处理有漏洞?有没有更可靠的方式在组件销毁时彻底清理ECharts实例,避免导航时出现重复实例的警告?
麻烦各位帮忙看看,谢谢啦!
内容来源于stack exchange
相关产品推荐
相关产品推荐

