You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular中使用Canvas动态创建饼图报错的问题排查与解决

问题

从后端获取长度动态变化的对象列表(有时6个,有时更少),需要实现饼图的动态创建。尝试通过循环列表创建图表,但运行时出现错误:

benchmarkchart.component.ts:61 Failed to create chart: can't acquire context from the given item

相关代码如下:

TS代码

makeChart() {
    for (let i = 0; i < this.benchmarkData.length; i++) {
      this.integrationActions = new Chart("PieChart" + i, {
        type: 'doughnut',
        data: {
          labels: this.benchmarkData[i].labels,
          datasets: [{
            label: 'Dataset 1',
            data: this.benchmarkData[i].values,
            backgroundColor: ['#299aff', '#ff4961', '#ffe802', '#9080e8', '#28dac6'],
            borderWidth: 0,
          }],
        },
        options: {
          aspectRatio: 3,
          responsive: true,
          legend: {
            position: 'left',
            display: true,
          }
        }
      });
    }
  }

HTML代码

<div class="row">
  <div class="col-lg-6 mt-3" *ngFor="let benchmarkDataIndex of benchmarkData; let i = index">
    <p>{{'PieChart'+ i}}</p>
    <div class="graph-section">
      <a href="javascript:void(0)"
         (click)="routBenchMark(benchmarkDataIndex.name,benchmarkDataIndex.policyId)">
        <h2>{{benchmarkDataIndex.name}}</h2></a>
      <div class="chart-container" [class.hidden]="showNoRecord === true">
        <canvas id="PieChart'+ i}}"></canvas>
      </div>
    </div>
  </div>

问题分析

  1. Canvas ID绑定语法错误:HTML中id="PieChart'+ i}}"的写法有误,渲染后会生成PieChart'+ i}}这类无效ID,导致Chart.js无法找到对应的DOM元素获取绘图上下文。
  2. 图表创建时机不匹配:如果makeChart()在数据刚获取时就调用(比如ngOnInit阶段),此时*ngFor还未完成DOM渲染,Canvas元素尚未生成,同样会引发找不到元素的错误。
  3. 实例管理混乱:用单个变量this.integrationActions存储所有图表实例,会导致后续实例覆盖之前的,无法对单个图表进行更新、销毁操作,容易引发内存泄漏。

修复方案

1. 修正Canvas的ID绑定

将HTML中Canvas的ID改为正确的插值语法,生成有效的ID:

<!-- 方式1:属性绑定 -->
<canvas [id]="'PieChart' + i"></canvas>

<!-- 方式2:插值表达式 -->
<canvas id="PieChart{{i}}"></canvas>

渲染后会生成PieChart0、PieChart1这类符合预期的ID。

2. 确保DOM渲染完成后创建图表

Angular中,*ngFor生成的DOM会在视图更新后才完全就绪,推荐结合ViewChildren和数据加载后的时机创建图表:

import { Component, ViewChildren, QueryList, ElementRef, AfterViewInit, ChangeDetectorRef } from '@angular/core';
import Chart from 'chart.js/auto';

// 用数组存储所有图表实例,方便管理
chartInstances: Chart[] = [];

// 通过模板引用变量获取所有Canvas元素
@ViewChildren('pieCanvas') pieCanvases!: QueryList<ElementRef<HTMLCanvasElement>>;

constructor(private cdr: ChangeDetectorRef) {}

// 模拟异步获取数据的方法
loadBenchmarkData() {
  this.yourDataService.getData().subscribe(data => {
    this.benchmarkData = data;
    // 手动触发变更检测,让*ngFor生成DOM
    this.cdr.detectChanges();
    // DOM就绪后创建图表
    this.makeChart();
  });
}

makeChart() {
  // 先销毁旧实例,避免内存泄漏
  this.chartInstances.forEach(chart => chart.destroy());
  this.chartInstances = [];

  // 遍历Canvas元素创建图表
  this.pieCanvases.forEach((canvasRef, index) => {
    const chart = new Chart(canvasRef.nativeElement, {
      type: 'doughnut',
      data: {
        labels: this.benchmarkData[index].labels,
        datasets: [{
          label: 'Dataset 1',
          data: this.benchmarkData[index].values,
          backgroundColor: ['#299aff', '#ff4961', '#ffe802', '#9080e8', '#28dac6'],
          borderWidth: 0,
        }],
      },
      options: {
        aspectRatio: 3,
        responsive: true,
        legend: {
          position: 'left',
          display: true,
        }
      }
    });
    this.chartInstances.push(chart);
  });
}

对应的HTML需要给Canvas添加模板引用变量:

<canvas #pieCanvas></canvas>

3. 避免内存泄漏

每次重新创建图表前,先销毁之前的所有图表实例(如上代码中的chart.destroy()),防止旧实例持续占用资源,尤其是在数据频繁更新的场景下。


内容的提问来源于stack exchange,提问作者sharjeel sandhu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 01:01:51