Aw3same
Aw3same

Reputation: 1030

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!

Upvotes: 1

Views: 4551

Answers (3)

Sergio M.
Sergio M.

Reputation: 125

Another cleaner solution

SelectionModel provides a method you can override named compareWith.

In my example I wrote a the private method isChecked() to implement its logic (it manage my TItem type but you can define yours or use any):


public selection = new SelectionModel<TItem>(true, [], false, this.isChecked());
    
private isChecked(): ((o1: TItem, o2: TItem) => boolean) | undefined {
    return (o1: TItem, o2: TItem) => {
        if (!o1.id || !o2.id) {
            return false;
        }

        return o1.id === o2.id;
    };
}

Upvotes: 0

AGivenDeveloper
AGivenDeveloper

Reputation: 31

I had this same issue. I haven't checked to see if the accepted answer works, but here is a cleaner alternative.

First, write a new method to determine whether a row is checked. This will be used to override the SelectionModel's comparison method:

isChecked(row: any): boolean {
    const found = this.selection.selected.find(el => el._id === row._id);
    if (found) {
      return true;
    }
    return false;
 }

Then in your ngOnInit, tell the SelectionModel to use your new method for comparison.

this.selection.isSelected = this.isChecked.bind(this);

With this method, you don't need to replace objects in the selection set at all.

(source: https://github.com/angular/components/issues/9670#issuecomment-455117080)

Upvotes: 3

JanMod
JanMod

Reputation: 446

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;

}

Upvotes: 4

Related Questions