2 mat-paginator for the same data source
Asked Answered
S

5

5

I have a mat-table that might show up to 100 entries per page. At first, I had one mat-paginator at the bottom that worked fine. Now I'm being asked to set a paginator at the top and another one at the bottom of the table, so the user won't have to scroll all the way down to reach the paginator if they are looking for an entry that is at the top.
The thing is that both paginators must be linked to the same data source. I´ve tried giving a different id for each one, using ViewChild to get them and assign them to the same data source, but it only works with one of them.

main.component.ts:

    flights: Flight[] =[];
    dataSource = new MatTableDataSource<Flight>(this.flights);
    @ViewChild('PAGINATOR') paginator: MatPaginator;
    @ViewChild('OTROPAGINATOR') paginatorOtro: MatPaginator;

main.component.html:

   <mat-paginator #PAGINATOR (page)="pageEvent = handlePage($event)" 
   [length]="length" [pageSizeOptions]="[25, 50, 100]" showFirstLastButtons> 
   </mat-paginator>

   <table mat-table ...> ... </table>

  <mat-paginator #OTROPAGINATOR (page)="pageEvent = handlePage($event)" 
  [length]="length" [pageSizeOptions]="[25, 50, 100]" showFirstLastButtons> 
  </mat-paginator>

Linking paginators to data source:

    this.dataSource = new MatTableDataSource<Flight>(this.flights);
    this.dataSource.paginator = this.paginator;
    this.dataSource.paginator = this.paginatorOtro;

Could someone guide me with this, please?

Storiette answered 25/1, 2019 at 23:26 Comment(4)
Best I could come up with is this stackblitz. The top paginator syncs perfectly to the bottom one, the bottom one unfortunately only changes the settings of the top one but doesn't update the table. Maybe it helps you anyway. Overall plan was to have two datasources, one for each paginator and then just sync the paginators.Rudiment
@FabianKüng I didn't think about creating 2 data sources. I think now I just need to find out how to properly update the first paginator and table content based on the changes of the second one, so I can simulate that both are working for the same table. Thank you!Storiette
Make sure to update the post if you find a way, I would be interested to know how you did it!Rudiment
@FabianKüng I found a way to replicate the desired behavior, using some logic with properties and methods (from the angular material paginator api). Just with a little limitation... I will publish an answer and explain it.Storiette
S
5

I couldn't find a way to set up 2 paginators for the same data souce. The way I did it, is setting up a second paginator with a copy of the data source, then moving each paginator based on the changes of the other one.

Also, I couldn't set the page index or items per page properties directly (the table was not refreshing), so all the movements are achieved with some logic and paginator methods like previousPage() or lastPage().

The only limitation is that the paginator at the bottom can't change items per page, since there isn't a method that lets me control that property.

You can see the result here. Thanks FabianKüng for the solution on using 2 data sources and syncing both paginators.

I hope this is useful for someone.

Storiette answered 28/1, 2019 at 18:0 Comment(1)
Great work man, I was just searching for this and found your answer is useful for meAcademic
O
2

thanks a lot Gustavo Alejandro for your solution. I used it into my angular v11 project.

A simple solution for this:

The only limitation is that the paginator at the bottom can't change items per page, since there isn't a method that lets me control that property.

is that inside the handlePageBottom() method is needed to emit an event for topPaginator with the new values:

handlePageTop(e): void {
    const {pageSize, pageIndex} = e;
    this.bottomPaginator.pageSize = pageSize;
    this.bottomPaginator.pageIndex = pageIndex;
  }

  handlePageBottom(e): void {
    const {pageSize, pageIndex} = e;
    this.topPaginator.pageSize = pageSize;
    this.topPaginator.pageIndex = pageIndex;
    this.topPaginator.page.emit(e);
  }

  ngAfterViewInit(): void {
    merge(
      this.topPaginator.page // we don't need both
    ).pipe(startWith({}),
      debounceTime(300)).subscribe() => {
       // do something
    });
  }
}

You dont need to import PageEvent from @angular/material/paginator, since your template will look like:

<mat-paginator #paginatorTop
               (page)="handlePageTop($event)" 
               [length]="resultsLength" 
               [pageSizeOptions]="[10, 25, 50, 100]" 
               [pageSize]="25">
</mat-paginator>
O answered 15/1, 2021 at 11:26 Comment(1)
Greate solution.Stearne
Q
1

Simplified solution for lazy loading/delayed loading/dynamic tables, with table visibility set by user invoked event: It uses one master MatPaginator and a second slave MatPaginator.

The difference here is to set the page size for the slave paginator after table is loaded and paginator is set using ngAfterContentChecked().

HTML:

    <mat-paginator #paginatorTop (page)="handlePaginatorBottom($event)" ... </mat-paginator>
    <table> ... </table>
    <mat-paginator #paginatorBottom (page)="handlePaginatorTop($event)" ... </mat-paginator>

TS:

    @ViewChild('paginatorTop', { static: false }) paginatorTop: MatPaginator;
    @ViewChild('paginatorBottom', { static: false }) paginatorBottom: MatPaginator;
    this.dataSource.paginator = this.paginatorTop;
    
    ngAfterContentChecked(): void {
        if (this.paginatorTop) {
          this.paginatorBottom.length = this.paginatorTop.length;
        }
    }

    handlePaginatorTop(e): void {
        const { pageSize, pageIndex } = e;
        this.paginatorTop.pageSize = pageSize
        this.paginatorTop.pageIndex = pageIndex;
        this.paginatorTop.page.emit(e);
    }

    handlePaginatorBottom(e): void {
        const { pageSize, pageIndex } = e;
        this.paginatorBottom.pageSize = pageSize
        this.paginatorBottom.length = this.paginatorTop.length;
        this.paginatorBottom.pageIndex = pageIndex;
      }
Quinta answered 9/6, 2022 at 23:25 Comment(1)
Note the 2x datasource solution is not viable for my needs due to size of the table (>1000 records) and client side performance impact using a dup datasource.Quinta
S
0

Insprired by the answer of @AlleXyS I created fully working code to accomplish mat-paginators at the top and bottom of material table by using the same datasource. Though I had to make some significant changes. The important thing is to connect the top paginator with the handlePaginatorBottom method and vice versa. That way the two stay in sync. When the bottom paginator is used inside handlePaginatorTop a page event is emitted to trigger the page observable of paginator one to load according data.

Here is the full implementation since its little tricky.

Template implementation:

<mat-paginator
    #paginatorTop
    (page)="handlePaginatorBottom($event)"
    [length]="resultsLength"
    [pageSize]="30"
  ></mat-paginator>
  
  <div class="table-container">
    <table
      mat-table
      [dataSource]="data"
      multiTemplateDataRows
    >
// ...
    </table>
 </div>
 <mat-paginator
      #paginatorBottom
      (page)="handlePaginatorTop($event)"
      [length]="resultsLength"
      [pageSize]="30"
    ></mat-paginator>

Class implementation:

// ...

@ViewChild('paginatorTop', { static: false })
paginatorTop: MatPaginator
@ViewChild('paginatorBottom', { static: false })
paginatorBottom: MatPaginator

// ...

ngAfterViewInit() {
        
        this.paginatorTop.page
            .pipe(
                startWith({}),
                switchMap(() => {
                    // ...
                    // use switch map to load data via another observable
                })
                map((data) => {
                    this.isLoadingResults = false
                    this.resultsLength = data.count
                    return data.items
                }),
                catchError(() => {
                    this.isLoadingResults = false
                    return of([])
                })
            )
            .subscribe((data) => {
                this.data = data
            })
    }

    handlePaginatorTop(e): void {
        const { pageSize, pageIndex } = e
        this.paginatorTop.pageSize = pageSize
        this.paginatorTop.pageIndex = pageIndex
        this.paginatorTop.page.emit(e)
    }

    handlePaginatorBottom(e): void {
        const { pageSize, pageIndex } = e
        this.paginatorBottom.pageSize = pageSize
        this.paginatorBottom.pageIndex = pageIndex
    }
Scarificator answered 19/3, 2021 at 3:54 Comment(0)
P
-1
dataSource = new MatTableDataSource;
@ViewChild('paginatorTop',{static:false}) topPaginator: MatPaginator;
@ViewChild('paginatorBottom',{static:false}) bottomPaginator: MatPaginator;
     
this.dataSource = new MatTableDataSource<any>(this.tableData);

Set api data to data source

Poinciana answered 29/7, 2020 at 11:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.