Angular中鼠标移动时isAllSelected()被频繁调用的原因排查
为什么Material Table的isAllSelected()会在鼠标移动时频繁调用?
嘿,这个问题我之前也碰到过,其实根源在于Angular的默认变更检测策略,我来给你拆解清楚:
问题原因
你在模板里把isAllSelected()直接绑定到了<mat-checkbox>的[checked]属性上:
<mat-checkbox (change)="$event ? masterToggle() : null" [checked]="selection.hasValue() && isAllSelected()"></mat-checkbox>
Angular默认的变更检测机制是每次页面触发事件(包括鼠标移动、滚动、输入框输入甚至定时器触发)时,都会遍历组件树,检查所有绑定的表达式是否变化。而方法调用(比如isAllSelected())每次执行都会被视为“可能返回不同值”,所以Angular会在每一次变更检测周期里都执行这个方法——这就是为什么鼠标移动时它会被调用几百次。
解决方案
我们需要避免在模板绑定中直接使用方法,换成以下两种更高效的方式:
方案1:缓存计算结果到变量(最直观)
在组件里维护一个变量存储“是否全选”的状态,只在必要的时候更新它,而不是每次变更检测都重新计算:
组件代码修改:
import { Component, OnInit, OnDestroy } from '@angular/core'; import { MatTableDataSource } from '@angular/material/table'; import { SelectionModel } from '@angular/cdk/collections'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { PriorityDto } from './your-path'; // 替换成你的实际路径 export class AdminPrioritiesSettingsComponent implements OnInit, OnDestroy { dataSource: MatTableDataSource<any>; selection = new SelectionModel<PriorityDto>(true, []); displayedColumns = ['checkbox', 'colour', 'name']; isAllSelectedValue = false; // 新增变量存储状态 private destroy$ = new Subject<void>(); // 用于清理订阅 ngOnInit() { // 监听选择状态变化,更新全选状态 this.selection.changed.pipe(takeUntil(this.destroy$)).subscribe(() => { this.updateIsAllSelected(); }); // 监听数据源变化,更新全选状态 this.dataSource.connect().pipe(takeUntil(this.destroy$)).subscribe(() => { this.updateIsAllSelected(); }); } ngOnDestroy() { // 清理订阅,防止内存泄漏 this.destroy$.next(); this.destroy$.complete(); } // 封装计算逻辑,只在必要时调用 private updateIsAllSelected() { const numSelected = this.selection.selected.length; const numRows = this.dataSource.data.length; this.isAllSelectedValue = numSelected === numRows; } masterToggle() { // 直接使用缓存的变量 this.isAllSelectedValue ? this.selection.clear() : this.dataSource.data.forEach(row => this.selection.select(row)); } }
模板代码修改:
<mat-checkbox (change)="$event ? masterToggle() : null" [checked]="selection.hasValue() && isAllSelectedValue"></mat-checkbox>
方案2:使用纯管道(更优雅)
创建一个纯管道,把全选的计算逻辑封装进去。纯管道的特点是只有当输入参数的引用发生变化时才会重新计算,不会在每次变更检测都执行:
创建管道:
import { Pipe, PipeTransform } from '@angular/core'; import { SelectionModel } from '@angular/cdk/collections'; @Pipe({ name: 'isAllSelected' }) export class IsAllSelectedPipe implements PipeTransform { transform(selection: SelectionModel<any>, data: any[]): boolean { if (!data || data.length === 0) return false; return selection.selected.length === data.length; } }
记得在你的模块里声明这个管道:
@NgModule({ declarations: [IsAllSelectedPipe], // ...其他配置 }) export class YourModule {}
模板里使用管道:
<mat-checkbox (change)="$event ? masterToggle() : null" [checked]="selection.hasValue() && (selection | isAllSelected: dataSource.data)"></mat-checkbox>
额外优化:开启OnPush变更检测
如果你的组件数据变化不频繁,可以把组件的变更检测策略改为OnPush,进一步减少不必要的检测:
import { Component, ChangeDetectionStrategy } from '@angular/core'; @Component({ selector: 'app-admin-priorities-settings', templateUrl: './admin-priorities-settings.component.html', styleUrls: ['./admin-priorities-settings.component.css'], changeDetection: ChangeDetectionStrategy.OnPush // 新增这一行 }) export class AdminPrioritiesSettingsComponent { // ...组件代码 }
这样Angular只会在组件的输入属性变化、组件内部触发markForCheck()或detectChanges()时才执行变更检测,能大幅降低检测频率。
内容的提问来源于stack exchange,提问作者pantonis
相关产品推荐
相关产品推荐

