42

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

mostafa cs
  • 718
  • 1
  • 8
  • 16
  • 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. – VtoCorleone May 11 '18 at 03:59
  • 1
    We was using html tables without angular and it was works perfectly and we want to do operations on bulk – mostafa cs May 11 '18 at 12:16
  • Nice observation. I am also facing the same issue even for 32 rows. – Aseer KT Miqdad Jul 20 '22 at 13:01

8 Answers8

92

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.

Turneye
  • 978
  • 6
  • 5
  • 16
    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. – rbasniak Dec 04 '18 at 20:55
  • 2
    I almost decided to go with another datatable library. Thanks mate. This performance issue should be at least specified in the documentation! – Hristo Enev Dec 16 '18 at 11:31
  • 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; – Priyanka Jun 04 '19 at 11:00
  • @Priyanka I get the same thing, did you ever resolve it? – americanslon Aug 07 '19 at 14:42
  • @Turneye: I'm having an issue with your solution here: https://stackoverflow.com/questions/58518445/not-able-to-set-initial-pageindex-in-mat-paginator-when-dealing-with-large-datas – Michael Hunziker Oct 23 '19 at 08:19
  • wow this drastically improved the performance of my Sort. Thank you – kjohnsonthecoder Dec 21 '19 at 04:29
  • For me helped to set the table data to ngAfterViewChecked – Dmytro K. Sep 17 '20 at 01:08
  • Thanks to your solution I managed to solve performance issues that was happening even loading bunchs of 300 data. Thank you so much! – WitnessTruth Nov 09 '20 at 12:56
  • I'm not using a pagination at all, similar to the example Basic use of `` on the docs, but I'm still getting very slow load times, 10 seconds for 300 items
    – DevEng Feb 04 '21 at 02:39
  • 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. – user7185558 Aug 01 '21 at 21:42
  • 1
    This solution throws change detection errors. I prefer @poul-kruijt solution below – Johan Faerch Nov 15 '21 at 07:47
18

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">
Poul Kruijt
  • 69,713
  • 12
  • 145
  • 149
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();
      });
 
    }
}
Kurtis Jungersen
  • 2,204
  • 1
  • 26
  • 31
  • Is this using server side sorting and pagination? – Manvir Singh Aug 30 '21 at 20:53
  • @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. – Kurtis Jungersen Aug 31 '21 at 04:25
6

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()

István
  • 5,057
  • 10
  • 38
  • 67
mostafa cs
  • 718
  • 1
  • 8
  • 16
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

jabu.hlong
  • 2,194
  • 20
  • 21
0

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.

atlowell
  • 11
  • 3
  • please elaborate on your solution in brief. – Ratan Uday Kumar Nov 07 '19 at 06:30
  • 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. – atlowell Nov 07 '19 at 07:02
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.

Boolean Autocrat
  • 347
  • 2
  • 16
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;
   });
 }
Max Sbadl
  • 13
  • 3