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

如何通过@Input()实现Angular父子组件图表数据传递?

解决Angular父子组件图表数据传递与复用问题

核心思路

把图表的初始化、配置生成、更新逻辑完全迁移到子组件EchartsComponent,父组件仅负责获取数据并通过@Input()传递给子组件,让子组件可以独立在任意地方复用。

具体修改步骤

1. 重构子组件 EchartsComponent

让子组件独立处理图表的所有逻辑,接收父组件传递的日志数据:

echarts.component.ts

import { Component, Input, OnInit, OnChanges, SimpleChanges, ViewChild, ElementRef, OnDestroy } from '@angular/core';
import * as echarts from 'echarts';
import { Logs } from './path-to-logs-interface'; // 替换成你的Logs接口实际路径
import { Subscription } from 'rxjs';

@Component({
  selector: 'app-echarts',
  templateUrl: './echarts.component.html',
  styleUrls: ['./echarts.component.css']
})
export class EchartsComponent implements OnInit, OnChanges, OnDestroy {
  sideNavStatus: boolean = false;
  private myChart!: echarts.ECharts;
  @Input() logs!: Logs[]; // 接收父组件传入的日志数据
  @ViewChild('chartContainer') chartContainer!: ElementRef<HTMLElement>; // 用ViewChild获取DOM容器,替代全局查找

  ngOnInit(): void {
    this.initChart();
    // 初始数据存在时直接渲染图表
    if (this.logs) {
      this.updateChart(this.logs);
    }
  }

  ngOnChanges(changes: SimpleChanges): void {
    // 监听数据变化,自动更新图表
    if (changes['logs'] && changes['logs'].currentValue) {
      this.updateChart(changes['logs'].currentValue);
    }
  }

  ngOnDestroy(): void {
    // 销毁图表实例,避免内存泄漏
    if (this.myChart) {
      this.myChart.dispose();
    }
  }

  private initChart(): void {
    this.myChart = echarts.init(this.chartContainer.nativeElement);
  }

  private updateChart(data: Logs[]): void {
    const option = this.initBasicEchart(data);
    this.myChart.setOption(option);
  }

  private initBasicEchart(data: Logs[]) {
    const result: any = {};

    data.forEach(el => {
      const date = el.date.toString().substring(0, 10);
      // 简化数据统计逻辑
      if (!result[el.event]) result[el.event] = {};
      result[el.event][date] = (result[el.event][date] || 0) + 1;
    });

    // 提取登录和注册的日期与数据
    const loginData = result.Login ? Object.values(result.Login) : [];
    const loginDates = result.Login ? Object.keys(result.Login) : [];
    const regData = result.Registration ? Object.values(result.Registration) : [];
    const regDates = result.Registration ? Object.keys(result.Registration) : [];

    // 合并并排序所有日期,修复原代码x轴数据错误
    const allDates = Array.from(new Set([...loginDates, ...regDates])).sort();

    // 补全缺失日期的0值,保证图表数据对齐
    const formattedLoginData = allDates.map(date => loginDates.includes(date) ? loginData[loginDates.indexOf(date)] : 0);
    const formattedRegData = allDates.map(date => regDates.includes(date) ? regData[regDates.indexOf(date)] : 0);

    return {
      title: { text: 'Graphic login and registration.' },
      tooltip: {},
      xAxis: { type: 'category', data: allDates },
      yAxis: {},
      series: [
        { name: 'Login', type: 'bar', data: formattedLoginData },
        { name: 'Registration', type: 'bar', data: formattedRegData }
      ]
    };
  }
}

echarts.component.html
修改DOM容器的引用方式,用模板变量替代全局ID:

<div class="container-fluid g-0">
    <app-header (sideNavToggled)="sideNavStatus = $event;"></app-header>

    <main>
        <app-sidebar [sideNavStatus]="sideNavStatus"
                     [ngClass]="{'app-side-nav-open': sideNavStatus}"></app-sidebar>

        <div class="display-area p-3" [ngClass]="{'display-area-shrink': sideNavStatus}">
            <!-- 用模板变量#chartContainer让ViewChild获取容器 -->
            <div #chartContainer style="width: 500px; height: 300px"></div>
        </div>
    </main>
</div>

<app-footer></app-footer>

2. 简化父组件 LogsComponent

父组件只保留数据获取逻辑,移除所有图表相关代码:

logs.component.ts

import { Component, OnInit, OnDestroy } from '@angular/core';
import { Logs } from './path-to-logs-interface'; // 替换成你的Logs接口实际路径
import { DataService } from './path-to-data-service'; // 替换成你的DataService实际路径
import { Subscription } from 'rxjs';

@Component({
  selector: 'app-logs',
  templateUrl: './logs.component.html',
  styleUrls: ['./logs.component.css']
})
export class LogsComponent implements OnInit, OnDestroy {
  sideNavStatus: boolean = false;
  private subscription!: Subscription;
  logs!: Logs[];

  columnDefs = [
    { headerName: 'Username', field: 'username', flex: 1},
    { headerName: 'Event', field: 'event', flex: 1 },
    { headerName: 'Date', field: 'date', flex: 1 }
  ];

  constructor(private dataService: DataService) {}

  ngOnInit() {
    this.LoadLogs();
  }

  ngOnDestroy(): void {
    // 清理订阅,避免内存泄漏
    if (this.subscription) {
      this.subscription.unsubscribe();
    }
  }

  LoadLogs(): void {
    this.subscription = this.dataService.getLogs().subscribe(logs => {
      this.logs = logs;
    });
  }
}

logs.component.html
给子组件添加输入绑定,传递日志数据:

<div class="container-fluid g-0">
    <app-header (sideNavToggled)="sideNavStatus = $event;"></app-header>

    <main>
        <app-sidebar [sideNavStatus]="sideNavStatus"
                     [ngClass]="{'app-side-nav-open': sideNavStatus}"></app-sidebar>

        <div class="display-area p-3" [ngClass]="{'display-area-shrink': sideNavStatus}">
            <p class="fs-1 fw-bold fst-italic text-center">Login and registration statistics.</p>

            <app-aggreed
                    *ngIf="logs"
                    [logs]="logs"
                    [columnDefs]="columnDefs"
            ></app-aggreed>

        </div>
    </main>
</div>

<!-- 传递logs数据给图表子组件 -->
<app-echarts [logs]="logs"></app-echarts>

<app-footer></app-footer>

关键优化点

  • 职责分离:图表逻辑完全归属于子组件,父组件专注数据获取,符合组件单一职责原则
  • 响应式更新:通过ngOnChanges监听数据变化,自动触发图表更新
  • DOM操作规范:用ViewChild替代document.getElementById,更贴合Angular组件生命周期
  • 修复图表bug:修正原代码x轴数据错误,合并排序所有日期并补全缺失值,保证图表数据对齐
  • 内存防护:在组件销毁时清理订阅和图表实例,避免内存泄漏

内容的提问来源于stack exchange,提问作者Артем Мишуровский

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 10:15:14