Angular 6 MatTable Performance in 1000 rows.

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

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">

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.