Mat-table checkbox select doesn't update the view after pagination.
The SelectionModel holds the "selected" property correctly, but loses view when you go to the next page.
app.component.ts
import {Component, AfterViewInit, ViewChild} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {MatPaginator, MatSort, MatTableDataSource} from '@angular/material';
import {Observable} from 'rxjs/Observable';
import {merge} from 'rxjs/observable/merge';
import {of as observableOf} from 'rxjs/observable/of';
import {catchError} from 'rxjs/operators/catchError';
import {map} from 'rxjs/operators/map';
import {startWith} from 'rxjs/operators/startWith';
import {switchMap} from 'rxjs/operators/switchMap';
import {SelectionModel} from '@angular/cdk/collections';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements AfterViewInit{
displayedColumns = ['select','id', 'name', 'progress', 'color'];
exampleDatabase: ExampleHttpDao | null;
dataSource = new MatTableDataSource();
selection = new SelectionModel<UserData>(true, []);
resultsLength = 0;
isLoadingResults = true;
isRateLimitReached = false;
@ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort;
constructor(private http: HttpClient) {}
ngAfterViewInit() {
this.exampleDatabase = new ExampleHttpDao(this.http);
// If the user changes the sort order, reset back to the first page.
this.sort.sortChange.subscribe(() => this.paginator.pageIndex = 0);
merge(this.sort.sortChange, this.paginator.page)
.pipe(
startWith({}),
switchMap(() => {
this.isLoadingResults = true;
return this.exampleDatabase!.getRepoIssues(
this.sort.active, this.sort.direction, this.paginator.pageIndex);
}),
map(data => {
// Flip flag to show that loading has finished.
this.isLoadingResults = false;
this.isRateLimitReached = false;
this.resultsLength = data.total_count;
return data.items;
}),
catchError(() => {
this.isLoadingResults = false;
// Catch if the GitHub API has reached its rate limit. Return empty data.
this.isRateLimitReached = true;
return observableOf([]);
})
).subscribe(data => this.dataSource.data = data);
this.dataSource!.paginator = this.paginator;
this.dataSource!.sort = this.sort;
}
applyFilter(filterValue: string) {
filterValue = filterValue.trim(); // Remove whitespace
filterValue = filterValue.toLowerCase(); // Datasource defaults to lowercase matches
this.dataSource.filter = filterValue;
}
/** Whether the number of selected elements matches the total number of rows. */
isAllSelected() {
const numSelected = this.selection.selected.length;
const numRows = this.dataSource.data.length;
return numSelected === numRows;
}
/** Selects all rows if they are not all selected; otherwise clear selection. */
masterToggle() {
this.isAllSelected() ?
this.selection.clear() :
this.dataSource.data.forEach(row => this.selection.select(row));
}
}
export interface UserDataApi {
data: UserData[];
total_count: number;
}
export interface UserData {
id: string;
name: string;
progress: string;
color: string;
}
/** An example database that the data source uses to retrieve data for the table. */
export class ExampleHttpDao {
constructor(private http: HttpClient) {}
getRepoIssues(sort: string, order: string, page: number): Observable<UserDataApi> {
const requestUrl = './assets/data.json';
return this.http.get<UserDataApi>(requestUrl);
}
}
app.component.html
<div class="example-header">
<mat-form-field>
<input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter">
</mat-form-field>
</div>
<mat-table #table [dataSource]="dataSource" class="example-table" matSort matSortActive="created" matSortDisableClear matSortDirection="asc">
<!--- Note that these columns can be defined in any order.
The actual rendered columns are set as a property on the row definition" -->
<!-- Checkbox Column -->
<ng-container matColumnDef="select">
<mat-header-cell *matHeaderCellDef>
<mat-checkbox (change)="$event ? masterToggle() : null"
[checked]="selection.hasValue() && isAllSelected()"
[indeterminate]="selection.hasValue() && !isAllSelected()">
</mat-checkbox>
</mat-header-cell>
<mat-cell *matCellDef="let row">
<mat-checkbox (click)="$event.stopPropagation()"
(change)="$event ? selection.toggle(row) : null"
[checked]="selection.isSelected(row)">
</mat-checkbox>
</mat-cell>
</ng-container>
<!-- Number Column -->
<ng-container matColumnDef="id">
<mat-header-cell *matHeaderCellDef mat-sort-header>id</mat-header-cell>
<mat-cell *matCellDef="let row">{{ row.id }}</mat-cell>
</ng-container>
<!-- Title Column -->
<ng-container matColumnDef="name">
<mat-header-cell *matHeaderCellDef mat-sort-header>Name</mat-header-cell>
<mat-cell *matCellDef="let row">{{ row.name }}</mat-cell>
</ng-container>
<!-- State Column -->
<ng-container matColumnDef="progress">
<mat-header-cell *matHeaderCellDef mat-sort-header>Progress</mat-header-cell>
<mat-cell *matCellDef="let row">{{ row.progress }}</mat-cell>
</ng-container>
<!-- State Column -->
<ng-container matColumnDef="color">
<mat-header-cell *matHeaderCellDef mat-sort-header>Color</mat-header-cell>
<mat-cell *matCellDef="let row">{{ row.color}}</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns;"
(click)="selection.toggle(row)">
</mat-row>
</mat-table>
<mat-paginator #paginator
[pageSize]="10"
[pageSizeOptions]="[5, 10, 20]">
</mat-paginator>


Please re-open with an example on Stackblitz so we can reproduce the issue and investigate.
@meli002 did you find the solution? I'm facing the same issue.
@meli002 @shridhar4 did you find the solution? I'm facing the same issue.
@meli002 @shridhar4 if you still follow this thread, did you find a working code solution? I am facing the very same issue
I have same issue
same issue here.
The SelectionModel is using a Set underneath to track what's been selected. If you're passing in an Object the Set will only return true if its the same Object in memory, otherwise you'll get what you're experiencing now.
When you make a selection on one page it's storing your UserData Object, then you change pages and go back which creates an entirely new Object so the Set doesn't see it as having been a selection.
Assuming you can't pull all data into the browser, track your selections using the identifier for your Object or some primitive value.
Hope this helps.
The SelectionModel is using a Set underneath to track what's been selected. If you're passing in an Object the Set will only return true if its the same Object in memory, otherwise you'll get what you're experiencing now.
When you make a selection on one page it's storing your UserData Object, then you change pages and go back which creates an entirely new Object so the Set doesn't see it as having been a selection.
Assuming you can't pull all data into the browser, track your selections using the identifier for your Object or some primitive value.
Hope this helps.
Wow, your comment definitely helped me figuring out the solution.
Every time mat-table changes page, selection "looses" track of the 'row' object. So when you try to select(row), deselect(row), toggle(row) after a page change, things will get messed up.
This will directly link to mat-checkbox's [check] not recognizing 'selection.isSelect(row)' to re-check the box when you go back to the previous page.
What I did is calling different functions during (change) [checked]
<mat-checkbox (click)="$event.stopPropagation()"
(change) ="$event.checked ? checked(row) : unChecked(row)"
[checked] ="isChecked(row)">
</mat-checkbox>
Then on TS side with the functions below. Note that I added a 'checked' boolean variable in my object model.
checked(row: any){
this.selection.select(row)
var found = this.selection.selected.find(x => x.documentId == row.documentId);
if (found) found.checked = true;
}
unchecked(row: any){
var found = this.selection.selected.find(x => x.documentId == row.documentId);
if (found) found.checked = false;
this.selection.deselect(found); }//This is very important to deselect 'found' instead of 'row' since selectionmodel will not be able to find 'row' to deselect after page change.
isChecked(row: any) {
var found = this.selection.selected.find(x => x.documentId == row.documentId);
if (found) return found.checked;
I spent 2 days debugging this, so hopefully this helped.
Thanks @timdao4,
Similarly, I managed to achieve same result with less code:
Assuming that you have and object with some unique identifier as your selection model type (as @bwronin mantioned SelectionModel uses Set)
// Element has unique '_id'
selection: SelectionModel<Element> = new SelectionModel<Element>(true, []);
I only needed to override the isSelected method and leave the SelectionModel to do its logic as usual.
isChecked(row: any): boolean {
const found = this.selection.selected.find(el => el._id === row._id);
if (found) {
return true;
}
return false;
}
Then, in ngOnInit:
this.selection.isSelected = this.isChecked.bind(this);
This allows me to keep the template (and the rest of the selection logic) untouched, as you find it in the Material docs:
<mat-checkbox
(click)="$event.stopPropagation()"
(change)="$event ? selection.toggle(element) : null"
[checked]="selection.isSelected(element)"
>
</mat-checkbox>
This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.
Read more about our automatic conversation locking policy.
_This action has been performed automatically by a bot._