如何通过@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,提问作者Артем Мишуровский
相关产品推荐
相关产品推荐

