angularangular-materialpagination

Angular Material Table with Selection Model and server side pagination


I have an Angular Material table with pagination and a selection model to get some rows to do stuff. Currently I'm changing the pagination with server-side pagination, but with this my selection model brokes. While i'm changing the page, I can add rows to the selection model, and works fine. But when I going back to a previous page, the rows (which are in the selection model) doesn't appear checked.

My selection model, with the methods are just equal to the ones published in the demo page of angular material:

  selection = new SelectionModel<ItemManagementViewDTO>(true, []);

  isAllSelected(): boolean {
    const numSelected = this.selection.selected?.length;
    const numRows = this.items?.length;
    return numSelected === numRows;
  }
 

  masterToggle(): void {
    this.isAllSelected() ? this.selection.clear() : this.items?.forEach(row => this.selection.select(row));
  }

  checkboxLabel(row?: ItemManagementViewDTO): string {
    if (!row) {
      return `${this.isAllSelected() ? 'select' : 'deselect'} all`;
    }
    return `${this.selection.isSelected(row) ? 'deselect' : 'select'} row ${row.idItem + 1}`;
  }

The html is simple:

  <table mat-table [dataSource]="items" class="mat-elevation-z8" matSort>
        <!-- Checkbox Column -->
        <ng-container matColumnDef="select">
          <th mat-header-cell *matHeaderCellDef>
            <mat-checkbox
              (change)="$event ? masterToggle() : null"
              [checked]="selection.hasValue() && isAllSelected()"
              [indeterminate]="selection.hasValue() && !isAllSelected()"
              [aria-label]="checkboxLabel()"
              color="primary"
            >
            </mat-checkbox>
          </th>
          <td mat-cell *matCellDef="let row">
            <mat-checkbox (click)="$event.stopPropagation()" (change)="$event ? selection.toggle(row) : null" [checked]="selection.isSelected(row)" [aria-label]="checkboxLabel(row)" color="primary">
            </mat-checkbox>
          </td>
        </ng-container>
<!-- ... -->
<mat-paginator [length]="totalItems" (page)="onPageChange($event)" [pageSizeOptions]="[5, 10, 20]" showFirstLastButtons></mat-paginator>

My OnPageChange with server-side pagination is:

  onPageChange(event: PageEvent): void {
    this.itemsServ.getPaginatedItems(this.paramsStored, event.pageIndex, event.pageSize).subscribe(r => {
      this.items = r.listDTO;
      this.totalItems = r.totalElements;
  }

What I am missing? Every time I change page, the source is refreshed, but the item still exists in the selection model (if I added it). Why it don't appear checked if it's the same item as in the previous call?

Thanks in advance!


Solution

  • The SelectionModel uses internally a Set to store all selected elements.

    SelectionModel.isSelected method uses the Set.has method which returns a boolean indicating whether an element exisists in a Set.

    Here is an example what happens if you use Set.has with a different object reference.

    var set1 = new Set();
    var obj1 = {'key1': 1};
    set1.add(obj1);
    
    set1.has(obj1);        // returns true
    set1.has({'key1': 1}); // returns false because they are different object references
    

    In your case if you switch back and forth the table has the same data but different references.

    One solution is to replace/remove the selected items with the new ones.

    onPageChange(event: PageEvent): void {
         this.itemsServ.getPaginatedItems(this.paramsStored, event.pageIndex, event.pageSize).subscribe(r => {
         this.items = r.listDTO;
         // replace old selected items with new ones
         const itemsToAdd= items.
               filter(item=> {
                  const foundItem = this.selection.selected.find(selectedItem=> selectedItem.id === item.id);
                  if(!foundItem) return;
                  // removes item from selection
                  this.selection.deselect(foundItem);
                  return item;
               });
          
         this.selection.select(itemsToAdd);
         this.totalItems = r.totalElements;
    

    }