5

I am having some troubles with my angular Material Application. I am using a datatable with a paginator. The data does not have to be sorted.

While loading the data I am showing a mat-spinner

<div *ngIf="schools !== undefined">
        <mat-spinner *ngIf="showSpinner" style="margin:0 auto;"></mat-spinner>
        <div *ngIf="!showSpinner">
            Keine Daten gefunden.
        </div>
</div>
<div *ngIf="schools !== undefined">
   <mat-table [dataSource]="dataSource"> 
      ...
   </mat-table>
   <mat-paginator [pageSizeOptions]="[10, 20, 50, 100]" [pageSize]="20" showFirstLastButtons></mat-paginator>
</div>

In the normal case I am loading the data and the spinner stops spinning when the data is loaded and then the data is shown. This works perfectly fine.

But with a table, which has about 400 rows I am having this problem:

When the data is loaded sucessfully the spinner gets very very slow for about a second and the the data table is shown with all contents. (seperated by paginator)

It takes about 400 milliseconds to load the data from the api and about 3 milliseconds to parse it so that shouldnt be the problem.

I am loading the data like that:

ngOnInit() {
    setTimeout(() => {
      this.showSpinner = false;
    }, 5000);

    this.userID = this.authService.getCurrentUser.uid;
    this.loadData();
}

loadData() {
    this.apiService.getSchools().subscribe((schoolsData: any) => {
      this.schools = this.refParser.parse(schoolsData);
      this.dataSource = new MatTableDataSource(this.schools);

      this.cdr.detectChanges();
      this.dataSource.paginator = this.paginator;
    });
}

I already found a post on stackoverflow which should help me (Angular 6 MatTable Performance in 1000 rows.). But it did not help me :(

I tried it like that:

ngOnInit() {
    setTimeout(() => {
      this.showSpinner = false;
    }, 5000);

    this.userID = this.authService.getCurrentUser.uid;
    //this.loadData();
    this.dataSource = new MatTableDataSource();

    this.dataSource.paginator = this.paginator;
}

ngAfterViewInit(){
    setTimeout(() => {
      this.apiService.getSchools().subscribe((schoolsData: any) => {
        this.schools = this.refParser.parse(schoolsData);
        this.dataSource.data = this.schools;
        this.cdr.detectChanges();
      })
    })
}

It did not give me any performance boost. The only thing which happend was, the paginator did not work anymore.

Does anybody know what could help me to improve the performance of my application?

Thank you for your answers:)

Edric
  • 24,639
  • 13
  • 81
  • 91
Benjamin_Ellmer
  • 87
  • 1
  • 1
  • 6
  • Also a useful answer that is similar to the chosen answer, but includes setting the paginator as static to allow it be set in `ngOnInit` - https://stackoverflow.com/a/61726721/1148107 – mtpultz Mar 27 '21 at 05:56

5 Answers5

6

I had face the same issue in past, Mat-Table handles Change Detection itself so it is not a concern of mat table, It is the Data Source which is being set in a wrong way.

If you pass all of your data in one go, it will render the whole data at first load only and will show the lag.

Since you are using paginator you should break your datasource into pageSize, so that only 20 elements will be rendered at load.

create a new instance of paginator : actualPaginator: MatPaginator;

@ViewChild(MatPaginator, {static: false})
  set paginator(value: MatPaginator) {
    this.actualPaginator = value;
  }

this.actualPaginator.pageIndex = Math.ceil(this.schools.length/this.actualPaginator.pageSize) - 1;

 let nextPageData: any[] = [];
 nextPageData = this.schools.slice((this.actualPaginator.pageSize * this.actualPaginator.pageIndex),(this.actualPaginator.pageSize * (this.actualPaginator.pageIndex + 1)));
 this.dataSource.data = nextPageData;

So distribute your this.schools data and load it per page size count, change the data source with next slot of this.schools data on next button click will resolve your issue.

You have to operate mat-paginator separately by creating new MatPaginator instance that is key solution.

Vivek Singh
  • 289
  • 1
  • 4
  • 1
    Thank you sooo much you totally solved my problem But still I am having some troubles with handling the paginator. Do you maybe have a source where handling with paginators is explained ? – Benjamin_Ellmer Jan 09 '20 at 13:03
  • FWIW I upvoted this answer but later found a clearer explanation for what is happening. It's all about the order in which you set data and paginator. The correct order is data last (so the paginator is there to prevent all your data from being rendered when it's passed in). See this answer: https://stackoverflow.com/a/51296374/5185252 – D. Veen Mar 23 '21 at 07:45
3

you should just swap places of 2 row of code.

your code

this.dataSource = new MatTableDataSource(this.schools);
...
this.dataSource.paginator = this.paginator;

you are telling material to render all your table and only after rendering slice it by pager

and new code

this.dataSource.paginator = this.paginator;
...
this.dataSource = new MatTableDataSource(this.schools);
dimson d
  • 1,488
  • 1
  • 13
  • 14
  • you have the right idea but execution is wrong. The problem is indeed as you describe: Paginator must be set before data is set. However, by instantiating a new dataSource last, you loose the paginator configuration. Don't recreate the dataSource, just set dataSource.data _after_ setting dataSource.paginator – corolla Dec 20 '21 at 13:21
3

For me the problem was assigning the dataSource on ngOnChanges, I changed it to be assigned in ngAfterViewInit and it worked way faster.

Yair Cohen
  • 2,032
  • 7
  • 13
0

you should disable the spinner when you are done with loading data:

loadData() {
    this.apiService.getSchools().subscribe((schoolsData: any) => {
      this.schools = this.refParser.parse(schoolsData);
      this.dataSource = new MatTableDataSource(this.schools);
      this.showSpinner = false; // here!

      this.cdr.detectChanges();
      this.dataSource.paginator = this.paginator;
    });

and maybe change your template like this:

<mat-spinner *ngIf="showSpinner" style="margin:0 auto;"></mat-spinner>
<div *ngIf="!shools && !showSpinner">
    Keine Daten gefunden.
</div>

<div *ngIf="dataSource">
   <mat-table [dataSource]="dataSource"> 
      ...
   </mat-table>
   <mat-paginator [pageSizeOptions]="[10, 20, 50, 100]" [pageSize]="20" showFirstLastButtons></mat-paginator>
</div>
slaesh
  • 16,659
  • 6
  • 50
  • 52
  • Unfortunately this could not solve my problem, but thank you, you are totally right that solution is a better way to work with the spinner. – Benjamin_Ellmer Jan 09 '20 at 13:02
0

@dimson-d has the right idea: You need to set paginator before setting data:

constructor:
this.dataSource = new MatTableDataSource([]);
...

ngAfterViewInit:
this.dataSource.paginator = this.paginator
...
this.dataSource.data = this.schools

This guarantees dataset will be paginated before being rendered in table. Don't instantiate a new MatTableDataSource when you receive data, as that will remove the paginator reference. Just set dataSource.data.

The reason your attempted solution caused paginator to stop working and not improve perf is likely because set the paginator reference in ngOnInit, at which point it's null (assuming you are using @ViewChild(MatPaginator)). Therefore you are still operating on the entire, unpaginated dataset.

corolla
  • 5,386
  • 1
  • 23
  • 21