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

Angular子组件间传值引发Mat Table选择卡顿与循环问题

问题分析

核心问题是组件间数据传递触发了无限循环的变更检测,根源在于:

  1. second-child组件每次选行时通过BehaviorSubject推送数据,first-child的subscribe回调里修改FormArray或重建表格数据源,触发Angular变更检测;
  2. 变更检测又会导致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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 20:43:17