angularangularjsangular-materialangular-material-tablemat-pagination

How to Synchronize mat-paginator Page Sizes?


I'm attempting to synchronize the page sizes of multiple mat-paginators that are nested across multiple child components.

I'm using a shared service to store the "global" page size.

However, the page size is being updated across all the mat-paginators but the actual page size of the paginators is not changing.

enter image description here

example issue

Shared Service:

import { EventEmitter, Injectable } from "@angular/core";
import { BehaviorSubject } from "rxjs";

@Injectable({
   providedIn: 'root'
 })
 export class PaginatorService {
   private pageSizeSubject = new BehaviorSubject<number>(10); // Default page size
   pageSize$ = this.pageSizeSubject.asObservable();
 
   setPageSize(pageSize: number) {
     this.pageSizeSubject.next(pageSize);
   }
 }

Child Table Component

<table mat-table [dataSource]="propertiesDataSource" multiTemplateDataRows class="mat-elevation-z0" id="properties-table" matSort>   
   <ng-container *ngFor="let column of propertiesTableColumns" [matColumnDef]="column">
      <th mat-header-cell *matHeaderCellDef mat-sort-header>{{ tableHeader[column] }}</th>
      <td mat-cell *matCellDef="let lease">
          <ng-container [ngSwitch]="column">
              <span *ngSwitchDefault>{{ lease[column] }}</span>
          </ng-container>
      </td>
  </ng-container>

   <tr mat-header-row *matHeaderRowDef="propertiesTableColumns; sticky:true"></tr>
   <tr mat-row *matRowDef="let property; columns: propertiesTableColumns" (click)="openProperty(property)" class="item-row"></tr> 
</table>
<mat-paginator [pageSizeOptions]="[10,25,50,100]" [pageSize]="pageSize" (page)="onPageSizeChange($event)" showFirstLastButtons>
</mat-paginator>
export class LocationDetailPropertyTableComponent implements OnInit, AfterViewInit {
   @Input() properties: PropertyDtoModel[] = [];
   @Input() locationCustomerID: number;
   @Input() dateFilter: Date = new Date();

   @ViewChild(MatPaginator) paginator: MatPaginator;

   public propertiesDataSource: MatTableDataSource<PropertyDtoModel> = new MatTableDataSource();
   public propertiesTableColumns = Object.keys(eTableHeader) as Array<keyof typeof eTableHeader>;
   public tableHeader = eTableHeader;
   public pageSize: number;

   constructor(
      private router: Router,
      private rentersLegalLiabilityService: RentersLegalLiabilityService,
      private paginatorService: PaginatorService,
      private chageDetectorRef: ChangeDetectorRef
   ) {}

   ngAfterViewInit(): void {
      this.propertiesDataSource.paginator = this.paginator;
   }

   ngOnInit() {
      this.propertiesDataSource = new MatTableDataSource(this.properties);

      this.paginatorService.pageSize$.subscribe(size => {
         this.pageSize = size;
         this.chageDetectorRef.detectChanges(); // Manually trigger a change detection
      });
   }

   onPageSizeChange(event: any) {
      this.paginatorService.setPageSize(event.pageSize);
    }

Parent Page Component

<table mat-table [dataSource]="locationDataSource" multiTemplateDataRows class="mat-elevation-z0" id="locations-table" matSort>
   <!-- column for Expand button -->
   <ng-container matColumnDef="ExpandButton">
      <th mat-header-cell *matHeaderCellDef></th>
      <td mat-cell *matCellDef="let location" (click)="$event.stopPropagation()">
         <button mat-icon-button (click)="expandedLocation=expandedLocation===location ? null : location"
            matTooltip="Expand">
            <span class="mat-button-wrapper">
               <mat-icon>
                  {{expandedLocation === location ? "expand_more": "chevron_right"}}
               </mat-icon>
            </span>
         </button>
      </td>
      <td mat-foter-cell *matFooterCellDef></td>
   </ng-container>
   
   <ng-container *ngFor="let header of tableHeaders" [matColumnDef]="header.key">
      <th mat-header-cell *matHeaderCellDef mat-sort-header>{{ header.label }}</th>
      <td mat-cell *matCellDef="let location">{{ location[header.key] }}</td>
      <td mat-footer-cell *matFooterCellDef>{{ getFooterContent(header) }}</td>
   </ng-container>

   <!-- Expanded content column -->
   <ng-container matColumnDef="expandedDetail">
      <td mat-cell *matCellDef="let location" [attr.colspan]="locationsTableColumns.length">
         <div [@detailExpand]="location == expandedLocation ? 'expanded' : 'collapsed'">
            <location-detail-property-table [properties]="location.LocationProperties" [locationCustomerID]="location.LocationCustomerID" [dateFilter]="dateFilter"></location-detail-property-table>
         </div>
      </td>
   </ng-container>

   <tr mat-header-row *matHeaderRowDef="locationsTableColumns; sticky:true"></tr>
   <tr mat-row *matRowDef="let location; columns: locationsTableColumns" class="item-row"></tr>
   <tr mat-row *matRowDef="let row; columns: ['expandedDetail']" class="detail-row"></tr>
   <tr mat-footer-row *matFooterRowDef="locationsTableColumns"></tr>
</table>

I've tried forcing the change detection with no luck. After changing the pagination from 10-25 on another expanded table all other paginators should be updated to 25 page size as well.


Solution

  • IIRC the MatTableDataSource only reacts to the emitted PageEvents, and simply changing the pageSize property does not emit those events.

    So you either have to manually calculate the correct new page and update the page or pageIndex on the paginator for it to emit the event or you can use the built-in _changePageSize(pageSize) method which will calculate the new pageIndex in a way to ensure the first item on the page will still be displayed when pageSize is applied, and then emit the new page event. I think _changePageSize(pageSize) already triggers change detection as well, so you should be able to remove it as well:

      this.paginatorService.pageSize$.subscribe(size => {
         this._changePageSize(size);
      });
    

    Here's a working, simplified sample on stackblitz