Angular 1000 行中的 6 个 MatTable 性能

Angular 6 MatTable Performance in 1000 rows

我在我的项目中使用 angular material,并且我使用 Mat-Table 为每个 table 渲染 1000 Product/row。 当将 table 的分页(我们使用后端分页)更改为 1000 行时,性能变得非常慢,我什至无法在文本框中书写。

我试图调试这个问题,所以我将日志放在一个列模板上,这样我就可以看到渲染是如何工作的。

即使我将鼠标悬停在 table headers 上,我也看到它会重新渲染所有行。 是否有可能控制变化检测就像 ChangeDetectionStrategy.OnPush

不确定这是否对您的情况有帮助,因为没有代码,但我们发现如果设置了大型数据集,MatTable 的加载速度会非常慢在您设置数据源之前分页器。

例如 - 这需要几秒钟来渲染...

dataSource: MatTableDataSource<LocationItem> = new MatTableDataSource();
@ViewChild(MatSort) sort: MatSort;
@ViewChild(MatPaginator) paginator: MatPaginator;

ngOnInit() {
  this.dataSource.data = [GetLargeDataSet];
}

ngAfterViewInit() {
  this.dataSource.sort = this.sort;
  this.dataSource.paginator = this.paginator;
}

...但这很快

ngOnInit() {
  // data loaded after view init 
}

ngAfterViewInit() {
  this.dataSource.sort = this.sort;
  this.dataSource.paginator = this.paginator;

  /* now it's okay to set large data source... */
  this.dataSource.data = [GetLargeDataSet];
}

顺便说一句,我们只是在第二次访问该组件时才发现此问题,因为来自服务器的大型数据集正在缓存中,并且在第二次访问该组件时立即可用。如果您想将该代码保留在 ngOnInit 函数中,另一种选择是将 .delay(100) 添加到您的可观察对象中。

无论如何,这可能对您的情况有帮助,也可能没有帮助。

我已经解决了这个问题,我通过在自定义(网格)组件中包装 table 并将组件的 changeDetection 控制为 ChangeDetectionStrategy.OnPush 来提高性能,当我想要渲染更新我使用 ChangeDetectorRef.detectChanges()

我发现 paginatorsort 有时不起作用。

我对超过 2000 行有用的是:

 ngAfterViewInit() {
      setTimeout(() => {
          this.getLargeDataSet.subscribe(largeDataSet => {
              this.dataSource.paginator = this.paginator;
              this.dataSource.sort = this.sort;
              this.dataSource.data = largeDataSet;
          });
      });
 }

超级快,从 10+秒到 2 秒:0

我对 Turneye 的回答有疑问 "expressionchangedafterithasbeencheckederror",所以我从 jabu.hlong 的回答中获得灵感。它将 5-10 秒的加载时间变成了不到一秒。

ngAfterViewInit() {
  this.dataSource.sort = this.sort;
  this.dataSource.paginator = this.paginator;

  setTimeout(() => {
    this.dataSource.data = getData(); // Or whatever your data source is
  });
}

旁注(之所以提及,是因为它似乎与我遇到的 class 属于同一个问题):我发现最好的做法是在分页器之前设置排序,因为我有运行 设置 matSortActive 时出现反向问题。

编辑:修复了括号和分号的问题。

扩展@Turneye 的回答。它发生的原因是因为分页器是在所有行都被渲染之后设置的,因为这就是 ngAfterViewInit 钩子告诉你的。

所以它首先呈现数据源中的所有行,然后它看到:"hey, I'm getting a paginator, let me just remove all the rows (except the pager count)"。这在大型数据集 and/or 复杂 table 单元格模板上显然非常慢。

您可以通过在 @ViewChild 上使用 {static: true} 选项来解决这个问题。这将使分页器在 ngOnInit 生命周期钩子内可用,并且在呈现任何行之前:

要盗用他的代码,可以改成这样,还是有一个快的table:

readonly dataSource: MatTableDataSource<LocationItem> = new MatTableDataSource();

@ViewChild(MatSort, { static: true }) sort: MatSort;
@ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;

ngOnInit() {
  this.dataSource.data = [ GetLargeDataSet ];
  this.dataSource.sort = this.sort;
  this.dataSource.paginator = this.paginator;
}

请注意,如果您的 mat-paginator 位于 *ngIf

之类的结构指令中,这将不起作用

另一个性能问题可能是您必须声明一个 trackBy 函数:

To improve performance, a trackBy function can be provided to the table similar to Angular’s ngFor trackBy. This informs the table how to uniquely identify rows to track how the data changes with each update.

<table mat-table [dataSource]="dataSource" [trackBy]="myTrackById">

从父组件向下传递的大型数据集 Observable

经过一番努力,我能够将来自这个 post 的许多不同答案组合起来,包括 and the OP's selected right answer, and also answers on another similar SO post, and

我有大约 1000 行需要用于页面上的父组件,以及几个子组件,包括一个分页的 MatTable 组件

数据在 <500 毫秒内加载,但页面平均需要 15 秒来呈现,因为最初我只是传递一个 MatTableDataSource 对象,其中已经分配了数据。

解决方法:

  1. 传递带有数据的可观察对象,然后在视图初始化后订阅它,设置 MatTableDataSource.data 属性 after 设置 MatPaginatorMatSort.
  2. Component 装饰器配置中将 changeDetection 设置为 ChangeDetectionStrategy.OnPush
  3. 在可观察体中设置MatDataSource.data 属性后,告诉angular用ChangeDetectorRef.detectChanges()
  4. 检测变化

现在完整的 DOM 总共在大约 1 秒内呈现,考虑到需要同时呈现的数据量,这很好。

这是一个精简的例子:

@Component({
    changeDetection: ChangeDetectionStrategy.OnPush,
    selector: 'app-dynamic-grid',
    templateUrl: './dynamic-grid.component.html',
    styleUrls: ['./dynamic-grid.component.scss'],
  })
  export class DynamicGridComponent implements OnInit, AfterViewInit {
    @Input() public dataSource$: Observable<any[]>;
  
    public matDataSource = new MatTableDataSource<any>();
  
    @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
    @ViewChild(MatSort, { static: true }) sort: MatSort;
  
    constructor(private changeDetectorRef: ChangeDetectorRef) {}
  
    ngOnInit(): void {}
  
    ngAfterViewInit() {
      this.matDataSource.paginator = this.paginator;
      this.matDataSource.sort = this.sort;
  
      this.dataSource$.subscribe(x => {
        this.matDataSource.data = x;

        // The important part:
        this.changeDetectorRef.detectChanges();
      });
 
    }
}

尽管有 [@turneye 的上述] 解决方案,我仍然面临这个问题。对我来说,解决方案基本上是通过执行以下操作将数据源的重新分配延迟到最后可能的时刻:

ngAfterViewInit() {
    const ds = new MatTableDataSource<ProcessDelaysTable>()
    ds.sort = this.sort
    ds.paginator = this.paginator
    ds.paginator.pageSizeOptions = [5, 10, 50, 100, this.dataPayload.length]
    ds.data = this.dataPayload
    this.dataSource = ds
}

本质上,出现此问题是因为您在 this.datasource 上更改了深度 属性,方法是在检查数据后更改其数据。但是,如果您将 this.datasource 分配给一个新的 DataSource 对象,您将更改其内存地址以解决所有冲突。

对我来说,它使用 jabus.hlong 的答案有效,但我将代码移至 ngOnInit()。那里不需要 setTimeout()。

ngOnInit() {
   this.getLargeDataSet.subscribe(largeDataSet => {
       this.dataSource.paginator = this.paginator;
       this.dataSource.sort = this.sort;
       this.dataSource.data = largeDataSet;
   });
 }