angular / components

Component infrastructure and Material Design components for Angular
https://material.angular.io
MIT License
24.22k stars 6.69k forks source link

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

Closed Abhijith-Nagaraja closed 6 years ago

Abhijith-Nagaraja commented 6 years ago

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

Abhijith-Nagaraja commented 6 years ago

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('');
    }
  }
andrewseguin commented 6 years ago

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

rain01 commented 6 years ago

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.

Abhijith-Nagaraja commented 6 years ago

@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('');
    }
mdhvfrnd commented 6 years ago

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

<mat-paginator *ngIf="allItems ?.length > 0" #paginator [pageSize]="50" [pageSizeOptions]="[1,2,4,20, 40,100,150,200]">

Sanafan commented 6 years ago

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

bresleveloper commented 6 years ago
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"

lpalbou commented 6 years ago

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

kerby82 commented 6 years ago

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

KumarSidharth commented 6 years ago

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.

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

mostafamahdijoo commented 6 years ago

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

dheerpandey commented 5 years ago

Thanks @Abhijith-Nagaraja your solution worked for me.

TimoPot commented 5 years ago

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

csiddu14 commented 5 years ago

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

Davel-11 commented 5 years ago

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

Shyam-Chen commented 5 years ago
-  @ViewChild(MatPaginator) paginator: MatPaginator;
- 
-  ngOnInit() {
-    this.dataSource.paginator = this.paginator;
-  }

+  @ViewChild(MatPaginator) set matPaginator(paginator: MatPaginator) {
+    this.dataSource.paginator = paginator;
+  }
sinswa28 commented 5 years ago

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

brandontg commented 4 years ago

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.

ThomasAdr commented 4 years ago
-  @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.

cisco336 commented 4 years ago

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;

mattiLeBlanc commented 4 years ago

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.

angular-automatic-lock-bot[bot] commented 4 years ago

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.