Reputation: 1030
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
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
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
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