Skip to content

Commit 07849e4

Browse files
committed
fix(table): not re-rendering when switching to a smaller set of data than the current page
Fixes the table not rendering correctly when going from a large set of data (e.g. 50 items and on page 10) to a small set of items (e.g. 1 item). The issue comes from the fact that the paginator doesn't emit events if they weren't generated by the user (see discussion on #12586). Fixes #14010.
1 parent d22f48c commit 07849e4

File tree

2 files changed

+54
-15
lines changed

2 files changed

+54
-15
lines changed

src/lib/table/table-data-source.ts

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ import {
1414
merge,
1515
Observable,
1616
of as observableOf,
17-
Subscription
17+
Subscription,
18+
Subject,
1819
} from 'rxjs';
1920
import {MatPaginator, PageEvent} from '@angular/material/paginator';
2021
import {MatSort, Sort} from '@angular/material/sort';
@@ -44,6 +45,9 @@ export class MatTableDataSource<T> extends DataSource<T> {
4445
/** Stream that emits when a new filter string is set on the data source. */
4546
private readonly _filter = new BehaviorSubject<string>('');
4647

48+
/** Used to react to internal changes of the paginator that are made by the data source itself. */
49+
private readonly _internalPageChanges = new Subject<void>();
50+
4751
/**
4852
* Subscription to the changes that should trigger an update to the table's rendered rows, such
4953
* as filtering, sorting, pagination, or base data changes.
@@ -211,9 +215,9 @@ export class MatTableDataSource<T> extends DataSource<T> {
211215
merge<Sort|void>(this._sort.sortChange, this._sort.initialized) :
212216
observableOf(null);
213217
const pageChange: Observable<PageEvent|null|void> = this._paginator ?
214-
merge<PageEvent|void>(this._paginator.page, this._paginator.initialized) :
218+
merge<PageEvent|void>(
219+
this._paginator.page, this._internalPageChanges, this._paginator.initialized) :
215220
observableOf(null);
216-
217221
const dataStream = this._data;
218222
// Watch for base data or filter changes to provide a filtered set of data.
219223
const filteredData = combineLatest(dataStream, this._filter)
@@ -276,14 +280,24 @@ export class MatTableDataSource<T> extends DataSource<T> {
276280
*/
277281
_updatePaginator(filteredDataLength: number) {
278282
Promise.resolve().then(() => {
279-
if (!this.paginator) { return; }
283+
const paginator = this.paginator;
284+
285+
if (!paginator) { return; }
280286

281-
this.paginator.length = filteredDataLength;
287+
paginator.length = filteredDataLength;
282288

283289
// If the page index is set beyond the page, reduce it to the last page.
284-
if (this.paginator.pageIndex > 0) {
285-
const lastPageIndex = Math.ceil(this.paginator.length / this.paginator.pageSize) - 1 || 0;
286-
this.paginator.pageIndex = Math.min(this.paginator.pageIndex, lastPageIndex);
290+
if (paginator.pageIndex > 0) {
291+
const lastPageIndex = Math.ceil(paginator.length / paginator.pageSize) - 1 || 0;
292+
const newPageIndex = Math.min(paginator.pageIndex, lastPageIndex);
293+
294+
if (newPageIndex !== paginator.pageIndex) {
295+
paginator.pageIndex = newPageIndex;
296+
297+
// Since the paginator only emits after user-generated changes,
298+
// we need our own stream so we know to should re-render the data.
299+
this._internalPageChanges.next();
300+
}
287301
}
288302
});
289303
}

src/lib/table/table.spec.ts

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import {DataSource} from '@angular/cdk/collections';
2-
import {Component, OnInit, ViewChild} from '@angular/core';
2+
import {Component, OnInit, ViewChild, AfterViewInit} from '@angular/core';
33
import {
44
async,
55
ComponentFixture,
@@ -151,14 +151,14 @@ describe('MatTable', () => {
151151
let dataSource: MatTableDataSource<TestData>;
152152
let component: ArrayDataSourceMatTableApp;
153153

154-
beforeEach(() => {
154+
beforeEach(fakeAsync(() => {
155155
fixture = TestBed.createComponent(ArrayDataSourceMatTableApp);
156156
fixture.detectChanges();
157157

158158
tableElement = fixture.nativeElement.querySelector('.mat-table');
159159
component = fixture.componentInstance;
160160
dataSource = fixture.componentInstance.dataSource;
161-
});
161+
}));
162162

163163
it('should create table and display data source contents', () => {
164164
expectTableToMatchContent(tableElement, [
@@ -197,6 +197,31 @@ describe('MatTable', () => {
197197
]);
198198
});
199199

200+
it('should update the page index when switching to a smaller data set from a page',
201+
fakeAsync(() => {
202+
// Add 20 rows so we can switch pages.
203+
for (let i = 0; i < 20; i++) {
204+
component.underlyingDataSource.addData();
205+
fixture.detectChanges();
206+
}
207+
208+
// Go to the last page.
209+
fixture.componentInstance.paginator.lastPage();
210+
fixture.detectChanges();
211+
212+
// Switch to a smaller data set.
213+
dataSource.data = [{a: 'a_0', b: 'b_0', c: 'c_0'}];
214+
fixture.detectChanges();
215+
tick();
216+
fixture.detectChanges();
217+
218+
expectTableToMatchContent(tableElement, [
219+
['Column A', 'Column B', 'Column C'],
220+
['a_0', 'b_0', 'c_0'],
221+
['Footer A', 'Footer B', 'Footer C'],
222+
]);
223+
}));
224+
200225
it('should be able to filter the table contents', fakeAsync(() => {
201226
// Change filter to a_1, should match one row
202227
dataSource.filter = 'a_1';
@@ -650,7 +675,7 @@ class MatTableWithWhenRowApp {
650675
<mat-paginator [pageSize]="5"></mat-paginator>
651676
`
652677
})
653-
class ArrayDataSourceMatTableApp implements OnInit {
678+
class ArrayDataSourceMatTableApp implements AfterViewInit {
654679
underlyingDataSource = new FakeDataSource();
655680
dataSource = new MatTableDataSource<TestData>();
656681
columnsToRender = ['column_a', 'column_b', 'column_c'];
@@ -673,9 +698,9 @@ class ArrayDataSourceMatTableApp implements OnInit {
673698
});
674699
}
675700

676-
ngOnInit() {
677-
this.dataSource!.sort = this.sort;
678-
this.dataSource!.paginator = this.paginator;
701+
ngAfterViewInit() {
702+
this.dataSource.sort = this.sort;
703+
this.dataSource.paginator = this.paginator;
679704
}
680705
}
681706

0 commit comments

Comments
 (0)