Checkbox column out of sync with view after pagination
See original GitHub issueBug, feature request, or proposal:
Mat-table checkbox select doesn’t update the view after pagination.
What is the current behavior?
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>


Issue Analytics
- State:
- Created 6 years ago
- Comments:10 (1 by maintainers)
Top Results From Across the Web
Grid with pagination and checkbox column, how to select only ...
I have 30 items in the grid and the pagination is set to 10, now you can only see 10 rows, 10 items....
Read more >angular - Material 2 Table: View not updating checkboxes
I've tried adding a child view and some other changes, but I can't figure it out. Edit: I logged some things and it...
Read more >[Material] How to maintain checked state of checkbox after ...
I can see that the SelectionModel array is maintaining itself throughout multiple pagination events. But for some reason the template is loading ...
Read more >Angular Material Data Table - Data Selection - YouTube
This video is part of the Angular Material In Depth Course - https://angular-university.io/course/angular-material-courseCheck out the PDF ...
Read more >MUI V5: Tables pt 2 (Sorting, Checkboxes, Pagination)
Part 1 for basic table explanation: https://youtu.be/rsl3tzKkolUIn this video we go over Material UI V5's Table component in a lot more ...
Read more >
Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free
Top Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found

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.
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:
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.