Components: Mat Paginator is not working properly along when used conditional rendering (*ngIf) on the outer div.

Created on 1 Mar 2018  路  22Comments  路  Source: angular/components

Bug, feature request, or proposal:

Bug

What is the expected behavior?

When conditional rendering is done using *ngIf on table on outer <div>, Mat-Paginator should paginate properly

What is the current behavior?

If table is inside a conditional *ngIf and is hidden to start with, then after the condition is satisfied and view is initialized, data is not paginated. And the paginator is displayed as below
screen shot 2018-02-28 at 7 43 58 pm

What are the steps to reproduce?

With conditional rendering where we can see the problem clearly
https://stackblitz.com/edit/angular-material2-issue-s1hkz9?file=app/app.component.html

Without conditional rendering, it works properly
https://stackblitz.com/edit/angular-material2-issue-3xb5aa?file=app/app.component.html

What is the use-case or motivation for changing an existing behavior?

We need to display a table with pagination only if certain condition is met

Which versions of Angular, Material, OS, TypeScript, browsers are affected?

"@angular/animations": "^5.2.0",
"@angular/cdk": "^5.2.0",
"@angular/common": "^5.2.0",
"@angular/compiler": "^5.2.0",
"@angular/core": "^5.2.0",
"@angular/forms": "^5.2.0",
"@angular/http": "^5.2.0",
"@angular/material": "^5.2.0",
"@angular/platform-browser": "^5.2.0",
"@angular/platform-browser-dynamic": "^5.2.0",
"@angular/router": "^5.2.0",
"typescript": "~2.5.3"

Is there anything else we should know?

NA

Most helpful comment

I have the solution. But I don't know whether this is the correct way or a workaround. Please feel free to close this issue, if this is how it is designed

This is also applicable to MatSort.

Remove the following piece of code from ngAfterViewInit

@ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort;
  ngAfterViewInit() {
    this.dataSource.paginator = this.paginator;
    this.dataSource.sort = this.sort;
  }

Add the following piece of code

  private paginator: MatPaginator;
  private sort: MatSort;

  @ViewChild(MatSort) set matSort(ms: MatSort) {
    this.sort = ms;
    this.setDataSourceAttributes();
  }

  @ViewChild(MatPaginator) set matPaginator(mp: MatPaginator) {
    this.paginator = mp;
    this.setDataSourceAttributes();
  }

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

    if (this.paginator && this.sort) {
      this.applyFilter('');
    }
  }

All 22 comments

I have the solution. But I don't know whether this is the correct way or a workaround. Please feel free to close this issue, if this is how it is designed

This is also applicable to MatSort.

Remove the following piece of code from ngAfterViewInit

@ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort;
  ngAfterViewInit() {
    this.dataSource.paginator = this.paginator;
    this.dataSource.sort = this.sort;
  }

Add the following piece of code

  private paginator: MatPaginator;
  private sort: MatSort;

  @ViewChild(MatSort) set matSort(ms: MatSort) {
    this.sort = ms;
    this.setDataSourceAttributes();
  }

  @ViewChild(MatPaginator) set matPaginator(mp: MatPaginator) {
    this.paginator = mp;
    this.setDataSourceAttributes();
  }

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

    if (this.paginator && this.sort) {
      this.applyFilter('');
    }
  }

Unfortunately this is an Angular-thing where ViewChild doesn't catch elements with *ngIf in ngAfterViewInit. Your workaround looks feasible to me

I have the same issue but I'm trying to load the data from api.
@Abhijith-Nagaraja What is applyFilter()? It says it doesn't exist in my code.

@rain01: applyFilter() method is a custom method. This is typically used to filter the datagrid/table.

Here is the sample for that

applyFilter(filterValue: string) {
        filterValue = filterValue.trim(); // Remove whitespace
        filterValue = filterValue.toLowerCase(); // Datasource defaults to lowercase matches
        this.dataSource.filter = filterValue;
    }

This will still work if you remove the following

if (this.paginator && this.sort) {
      this.applyFilter('');
    }

Hi when we put the condition still this is not working.


the amount found items gets displayed correctly but pagination is still not working correctly :( how do i add another paginator and sort to your example @Abhijith-Nagaraja ? I have two tables in one component and cant get both paginators and sortings to work

constructor(private s:StubService) {
    console.log('welcome to TableComponent')
    this.dataSource = new MatTableDataSource([]);

    this.s.data.subscribe(allitems => {
      console.log('TableComponent data subscribe')
      this.dataSource = new MatTableDataSource(allitems);
      this.dataSource.paginator = this.paginator;
      this.dataSource.sort = this.sort;
    })
  }

notice the new MatTableDataSource([]);, that was my solution instead of *ngIf="dataSource"

Yes the *ngIf breaks the Material Paginator, however, instead of using *ngIf, you could use the [hidden] attribute like this:

<div [hidden]="isLoading">
  <mat-table [dataSource]="dataSource">
  ...
  </mat-table>
</div>

I have tested it on Angular 5 and it does work

Same here the *ngIF break the paginator. I guess this is a bug.

As suggested by @lpalbou *ngIf breaks mat-paginator and mat-sort.
The reason is that the value of paginator/sort in your ts file is not instantiated until the component renders it on your DOM.
Therefore you need to make sure that your DataSource object is generated after paginator renders on the DOM.
There are numerous ways to handle the issue.

  • [hidden] as suggested is one.

  • You may be getting data from an API call. You may initially show the table and hide it once you make an API call. After you get the data from API call show the table again.

  • Giving the opacity of the table as 0 initially. Turning opacity to 1 after you get the Data. You can add CSS transitions to it.

  • Giving the table width and height to 0 initially. Changing width and height to the desired value with a CSS transition.

You can take the code for CSS transitions from animista.com

I had the same issue, and using "[hidden]" attribute as @lpalbou said, solved my problem.
thanks @lpalbou .

Thanks @Abhijith-Nagaraja your solution worked for me.

Please document the issue and solution on https://material.angular.io/components/paginator/api

If multiple pagination are used in same page then we should use @ViewChildren

Example:
@ViewChildren(MatPaginator) paginator = new QueryList();

table1:
this.followUpListData.paginator=this.paginator.toArray()[0];

table2:
this.activeListData.paginator=this.paginator.toArray()[1];

Yes the *ngIf breaks the Material Paginator, however, instead of using *ngIf, you could use the [hidden] attribute like this:

<div [hidden]="isLoading">
  <mat-table [dataSource]="dataSource">
  ...
  </mat-table>
</div>

I have tested it on Angular 6 and it does work, THANK YOUUUU!!!

-  @ViewChild(MatPaginator) paginator: MatPaginator;
- 
-  ngOnInit() {
-    this.dataSource.paginator = this.paginator;
-  }

+  @ViewChild(MatPaginator) set matPaginator(paginator: MatPaginator) {
+    this.dataSource.paginator = paginator;
+  }

I had also faced same issue with Paginator and sorting with *ngIf . [hidden] attribute solved my issue too. Thank you so much @lpalbou !! :)

I tried applying the [hidden] attribute to a mat-card element wrapped around the mat-table, but that did not work; however, wrapping the card in a div and adding the [hidden] attribute to that did work.

-  @ViewChild(MatPaginator) paginator: MatPaginator;
- 
-  ngOnInit() {
-    this.dataSource.paginator = this.paginator;
-  }

+  @ViewChild(MatPaginator) set matPaginator(paginator: MatPaginator) {
+    this.dataSource.paginator = paginator;
+  }

@ViewChild(MatPaginator, {static: true}) set matPaginator(paginator: MatPaginator) { this.dataSource.paginator = paginator; }

A quick and easy way to fix the issue, idk why this answer is not among the most upvoted.

Hi guys, I just got mine working by making this:
Change: @ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
to: @ViewChild(MatPaginator, { static: false }) paginator: MatPaginator;

and then after I get the data from subscribe or promise:
this.dataSource.paginator = this.paginator;

I just wanted to make the paginator optional in my Table component, so I went for the SET solution.

<div class="mat-elevation-z8">
  <table mat-table [dataSource]="dataSource">

    <!-- Position Column -->
    <ng-container matColumnDef="position">
      <th mat-header-cell *matHeaderCellDef> No. </th>
      <td mat-cell *matCellDef="let element"> {{element.position}} </td>
    </ng-container>
    ......

    <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
    <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
  </table>
  <mat-paginator [pageSizeOptions]="[5, 10, 20]" showFirstLastButtons *ngIf="pagination"></mat-paginator>
</div>
export class TableComponent implements OnInit {
  @Input('pagination') pagination: boolean;
  @ViewChild(MatPaginator,  {static: false}) set matPaginator(paginator: MatPaginator) {
    if (this.pagination) {
      this.dataSource.paginator = paginator;
    }
  }
  displayedColumns: string[] = ['position', 'name', 'weight', 'symbol'];
  dataSource = new MatTableDataSource<PeriodicElement>(ELEMENT_DATA);
  ....
}

Seems to me the issue happens when Angular initialises the datasource before the template has been rendered. So when the Mat-pagination is missing, it doesn't seem to initialise the datasource properly. The Set Fix in the ViewChild as proposed by people above works for now.

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

_This action has been performed automatically by a bot._

Was this page helpful?
0 / 5 - 0 ratings