Angular 6 MatTable Performance in 1000 rows
Asked Answered
A

8

45

I'm using angular material in my project and I'm using Mat-Table to render 1000 Product/row per table. When Change pagination (we use backend pagination) of table to 1000 rows the performance become very slow I even can't write in textboxes.

I tried to debug the issue so I put logs on one column template so I can see how's render works.

I see it's Rerender all rows even if I hover on the table headers. Is there's any possibilities to control the change detection to be like ChangeDetectionStrategy.OnPush

enter image description here

Asha answered 11/5, 2018 at 1:22 Comment(3)
Why are you pulling 1000 rows? That's a lot of data coming across the wire. And almost no matter which framework you're using, you're going to see a sluggish behavior with that much rendering.Rasmussen
We was using html tables without angular and it was works perfectly and we want to do operations on bulkAsha
Nice observation. I am also facing the same issue even for 32 rows.Sty
A
5

I had solved this issue and I improved the performance by wrapping the table in custom (grid) component and Control the changeDetection of the component to be ChangeDetectionStrategy.OnPush and when I want to render update I used ChangeDetectorRef.detectChanges()

Asha answered 9/4, 2019 at 1:55 Comment(4)
Can you expand on this please?Hourihan
I have tried doing that, but when I hover on headers, it still renders everything againMolecular
Not relevant for any use casesStorz
is it related to : github.com/angular/components/issues/… ?Exum
E
101

Not sure if this will help your situation as there's no code but we've found that the MatTable loads very slowly if a large data set is set before you set the datasource paginator.

For example - this takes several seconds to render...

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;
}

...but this is fast

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];
}

Incidentally, we were only finding this issue the second time we access the component as the large dataset from server was being cached and was immediately available the second time component was accessed. Another option is to add .delay(100) to your observable if you want to leave that code in the ngOnInit function.

Anyway, this may or may not help your situation.

Excurvate answered 12/7, 2018 at 2:5 Comment(11)
I wish I could upvote this 1 million times! Too bad I decided to try to search for an answer after I spent 2 days profilling my entire application thinking that I had some code being called many times and decreasing the table performance because even with 100 items in the datasource it was getting very slow to update.Giesecke
I almost decided to go with another datatable library. Thanks mate. This performance issue should be at least specified in the documentation!Dolabriform
Without assigning data to datasource if I set first paginator I'm getting paginator undefined .. please help ! This is how I've done - this.dataSource = new MatTableDataSource(this.reportData); this.dataSource.paginator = this.paginator; this.dataSource.sort = this.sort;Emanuel
@Emanuel I get the same thing, did you ever resolve it?Hooge
@Turneye: I'm having an issue with your solution here: #58518945Monarda
wow this drastically improved the performance of my Sort. Thank youNoletta
For me helped to set the table data to ngAfterViewCheckedTandem
Thanks to your solution I managed to solve performance issues that was happening even loading bunchs of 300 data. Thank you so much!Suppository
I'm not using a pagination at all, similar to the example Basic use of <table mat-table> on the docs, but I'm still getting very slow load times, 10 seconds for 300 itemsCarin
I struggled for hours with the slowness of MATTABLE loaded with few thousand lines when I returned from another page, the solution was simple, display the table only when *ngIf true, I set it true at ngAfterViewInit.Subedit
This solution throws change detection errors. I prefer @poul-kruijt solution belowBairn
B
20

To extend upons @Turneye 's answer. The reason it's happening is because the paginator is being set after all the rows have been rendered, because that's what the ngAfterViewInit hook tells you.

So it first renders all rows from data source, then it sees: "hey, I'm getting a paginator, let me just remove all the rows (except the pager count)". This is obviously very slow on large data sets and/or complex table cell templates.

You can solve this by using the {static: true} option on your @ViewChild. This will make the paginator available inside the ngOnInit lifecycle hook, and before any row has been rendered:

To steal his code, you can change it to this, and still have a fast 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;
}

Be aware though that this will not work if your mat-paginator is inside a structural directive like *ngIf


Another performance issue could be that you have to declare a trackBy function:

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">
Beni answered 11/5, 2020 at 9:46 Comment(3)
{ static: true } worked for me. I was getting change detection errors using AfterViewInit, but didn't know I could push this up into OnInit. Super helpful!Attah
This trick speed up the rendering speed of my 3000-row table from 5 secs to 0 sec.Xenophobe
amazing!!! thanksWideeyed
R
10

Large DataSet Observable Passed Down From Parent Component

After much struggling, I was able to combine pieces of many different answers from this post, including @turneye's above and the OP's selected right answer, and also answers on another similar SO post, here and here.

I had ~1000 rows that I needed for a parent component on the page, as well as several child components, including a paginated MatTable component.

The data was loading in <500ms, but then the page would take, on average, 15 seconds to render, as originally I was just passing a MatTableDataSource object with the data already assigned.

The solution:

  1. Pass an observable with the data, and then subscribe to it after the view initializes, setting the MatTableDataSource.data property after setting the MatPaginator and MatSort.
  2. Set changeDetection to ChangeDetectionStrategy.OnPush in the Component decorator config.
  3. After setting the MatDataSource.data property in the observable body, tell angular to detect changes with ChangeDetectorRef.detectChanges()

Now the full DOM is rendered in ~1 second total, which is fine given the volume of data that needs to be present at once.

Here's a stripped down example:

@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();
      });
 
    }
}
Ratio answered 7/10, 2020 at 1:5 Comment(2)
Is this using server side sorting and pagination?Molecular
@ManvirSingh nope, this is assuming you're fetching the whole dataset at once. The data I've had have never been too large to warrant server-side pagination - just the issue with rendering, which I've since resolved.Ratio
A
5

I had solved this issue and I improved the performance by wrapping the table in custom (grid) component and Control the changeDetection of the component to be ChangeDetectionStrategy.OnPush and when I want to render update I used ChangeDetectorRef.detectChanges()

Asha answered 9/4, 2019 at 1:55 Comment(4)
Can you expand on this please?Hourihan
I have tried doing that, but when I hover on headers, it still renders everything againMolecular
Not relevant for any use casesStorz
is it related to : github.com/angular/components/issues/… ?Exum
D
4

I have found the paginator and sort to sometimes not work.

What has worked for me with over 2000 rows was:

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

Supper fast, from 10+sec to 2sec :0

Dulciana answered 2/9, 2019 at 7:57 Comment(2)
Are y using backend sorting and pagination or frontend ?Asha
Frontend @mostafacsDulciana
P
1

I was having an issue with Turneye's answer giving me an "expressionchangedafterithasbeencheckederror", so I took inspiration from jabu.hlong's answer. It turned 5-10 seconds of load time into less than a second.

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

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

A side note (mentioned because it seems to be in the same class of issue as the one I was having): I have found it best practice to set the sort before the paginator, because I have run into issues with the reverse when setting matSortActive.

EDIT: Fixed issues with brackets and semicolons.

Proceleusmatic answered 7/11, 2019 at 6:7 Comment(3)
please elaborate on your solution in brief.Depress
I'm not sure what you mean by elaborate? Setting a timeout seemed to work, likely because it delayed the setting of the data source until after the table had been fully initialized. It's probably a bit hacky, and there might be a better way to do it, but it fixed my problem.Proceleusmatic
I didn’t have to do timeout. But pagination move to after view init is the key.Discern
F
0

I was still facing the problem despite [@turneye's above] solution. For me the solution was to essentially delay the reassignment of the datasource to the last possible moment by doing the following:

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
}

Essentially this issue occurs because you are changing a deep property on this.datasource by changing its data after its been checked. However, if you assign this.datasource to a new DataSource object, you change its memory address resolving all conflicts.

Forb answered 28/1, 2021 at 5:1 Comment(0)
A
0

For me it worked using jabus.hlong's answer, but I moved the code to ngOnInit(). There the setTimeout() is not needed.

ngOnInit() {
   this.getLargeDataSet.subscribe(largeDataSet => {
       this.dataSource.paginator = this.paginator;
       this.dataSource.sort = this.sort;
       this.dataSource.data = largeDataSet;
   });
 }
Absolve answered 27/5, 2022 at 7:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.