Angular子组件间传值引发Mat Table选择卡顿与循环问题
问题分析
核心问题是组件间数据传递触发了无限循环的变更检测,根源在于:
- second-child组件每次选行时通过BehaviorSubject推送数据,first-child的subscribe回调里修改FormArray或重建表格数据源,触发Angular变更检测;
- 变更检测又会导致subscribe回调重复执行,形成循环,最终引发卡顿和控制台重复输出。
具体修复方案
1. 优化first-child组件的订阅逻辑
不在subscribe回调里重建整个FormArray或MatTableDataSource,只更新数据差异,避免不必要的变更检测:
// first-child.component.ts import { Component, OnInit, OnDestroy } from '@angular/core'; import { FormBuilder, FormArray } from '@angular/forms'; import { DataService } from '../data.service'; import { MatTableDataSource } from '@angular/material/table'; import { Subscription } from 'rxjs'; @Component({ selector: 'app-first-child', templateUrl: './first-child.component.html', }) export class FirstChildComponent implements OnInit, OnDestroy { dataSource = new MatTableDataSource<any>([]); displayedColumns = ['select', 'name', 'value']; selectionForm: FormArray; private dataSub: Subscription; constructor(private fb: FormBuilder, private dataService: DataService) { this.selectionForm = this.fb.array([]); } ngOnInit(): void { this.dataSub = this.dataService.selectedData$.subscribe(selectedRows => { this.updateSelectionForm(selectedRows); // 仅更新数据源数据,不重建实例 this.dataSource.data = selectedRows; }); } private updateSelectionForm(selectedRows: any[]): void { // 清空现有表单控件 while (this.selectionForm.length) { this.selectionForm.removeAt(0); } // 添加新控件 selectedRows.forEach(() => { this.selectionForm.push(this.fb.control(true)); }); } ngOnDestroy(): void { this.dataSub?.unsubscribe(); } }
2. 避免在ngOnChanges中重复创建MatTableDataSource
在second-child组件的ngOnChanges里复用数据源实例,仅更新data属性:
// second-child.component.ts import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; import { MatTableDataSource } from '@angular/material/table'; import { SelectionModel } from '@angular/cdk/collections'; import { DataService } from '../data.service'; @Component({ selector: 'app-second-child', templateUrl: './second-child.component.html', }) export class SecondChildComponent implements OnChanges { @Input() tableData: any[]; dataSource = new MatTableDataSource<any>([]); displayedColumns = ['select', 'name', 'value']; selection = new SelectionModel<any>(true, []); constructor(private dataService: DataService) {} ngOnChanges(changes: SimpleChanges): void { if (changes['tableData'] && changes['tableData'].currentValue) { // 复用现有数据源实例,仅更新数据 this.dataSource.data = changes['tableData'].currentValue; this.selection.clear(); } } isAllSelected(): boolean { const numSelected = this.selection.selected.length; const numRows = this.dataSource.data.length; return numSelected === numRows; } masterToggle(): void { this.isAllSelected() ? this.selection.clear() : this.dataSource.data.forEach(row => this.selection.select(row)); this.dataService.setData(this.selection.selected); } onRowSelection(): void { this.dataService.setData(this.selection.selected); } }
3. 用Async管道简化订阅(可选)
在first-child模板中使用async管道,让Angular高效管理变更检测和订阅:
<!-- first-child.component.html --> <table mat-table [dataSource]="dataService.selectedData$ | async" class="mat-elevation-z8"> <ng-container matColumnDef="select"> <th mat-header-cell *matHeaderCellDef> <mat-checkbox (change)="$event ? masterToggle() : null" [checked]="selection.hasValue() && isAllSelected()" [indeterminate]="selection.hasValue() && !isAllSelected()"> </mat-checkbox> </th> <td mat-cell *matCellDef="let row; let i = index"> <mat-checkbox [formControl]="selectionForm.at(i)" (change)="onRowSelection(row)"></mat-checkbox> </td> </ng-container> <ng-container matColumnDef="name"> <th mat-header-cell *matHeaderCellDef> Name </th> <td mat-cell *matCellDef="let row"> {{row.name}} </td> </ng-container> <ng-container matColumnDef="value"> <th mat-header-cell *matHeaderCellDef> Value </th> <td mat-cell *matCellDef="let row"> {{row.value}} </td> </ng-container> <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr> <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr> </table>
对应TS代码调整:
// first-child.component.ts ngOnInit(): void { this.dataSub = this.dataService.selectedData$.subscribe(selectedRows => { this.updateSelectionForm(selectedRows); }); }
关键优化点
- 复用数据源实例:无论哪个子组件,都不要频繁新建
MatTableDataSource,仅更新现有实例的data属性,减少对象创建和变更检测触发。 - 最小化更新操作:subscribe回调里只做必要的数据更新,不重建表单或表格结构,避免触发循环变更检测。
- 清理订阅:组件销毁时取消订阅,防止内存泄漏和异常数据接收。
内容的提问来源于stack exchange,提问作者angular
相关产品推荐
相关产品推荐

