Skip to content

fix(table): not re-rendering when switching to a smaller set of data than the current page #14665

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 22 additions & 8 deletions src/lib/table/table-data-source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import {
merge,
Observable,
of as observableOf,
Subscription
Subscription,
Subject,
} from 'rxjs';
import {MatPaginator, PageEvent} from '@angular/material/paginator';
import {MatSort, Sort} from '@angular/material/sort';
Expand Down Expand Up @@ -44,6 +45,9 @@ export class MatTableDataSource<T> extends DataSource<T> {
/** Stream that emits when a new filter string is set on the data source. */
private readonly _filter = new BehaviorSubject<string>('');

/** Used to react to internal changes of the paginator that are made by the data source itself. */
private readonly _internalPageChanges = new Subject<void>();

/**
* Subscription to the changes that should trigger an update to the table's rendered rows, such
* as filtering, sorting, pagination, or base data changes.
Expand Down Expand Up @@ -211,9 +215,9 @@ export class MatTableDataSource<T> extends DataSource<T> {
merge<Sort|void>(this._sort.sortChange, this._sort.initialized) :
observableOf(null);
const pageChange: Observable<PageEvent|null|void> = this._paginator ?
merge<PageEvent|void>(this._paginator.page, this._paginator.initialized) :
merge<PageEvent|void>(
this._paginator.page, this._internalPageChanges, this._paginator.initialized) :
observableOf(null);

const dataStream = this._data;
// Watch for base data or filter changes to provide a filtered set of data.
const filteredData = combineLatest(dataStream, this._filter)
Expand Down Expand Up @@ -276,14 +280,24 @@ export class MatTableDataSource<T> extends DataSource<T> {
*/
_updatePaginator(filteredDataLength: number) {
Promise.resolve().then(() => {
if (!this.paginator) { return; }
const paginator = this.paginator;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we creating a new const for this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We were using this.paginator in 4-5 different places. I moved it out into a const because it minifies better.


if (!paginator) { return; }

this.paginator.length = filteredDataLength;
paginator.length = filteredDataLength;

// If the page index is set beyond the page, reduce it to the last page.
if (this.paginator.pageIndex > 0) {
const lastPageIndex = Math.ceil(this.paginator.length / this.paginator.pageSize) - 1 || 0;
this.paginator.pageIndex = Math.min(this.paginator.pageIndex, lastPageIndex);
if (paginator.pageIndex > 0) {
const lastPageIndex = Math.ceil(paginator.length / paginator.pageSize) - 1 || 0;
const newPageIndex = Math.min(paginator.pageIndex, lastPageIndex);

if (newPageIndex !== paginator.pageIndex) {
paginator.pageIndex = newPageIndex;

// Since the paginator only emits after user-generated changes,
// we need our own stream so we know to should re-render the data.
this._internalPageChanges.next();
}
}
});
}
Expand Down
41 changes: 34 additions & 7 deletions src/lib/table/table.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {DataSource} from '@angular/cdk/collections';
import {Component, OnInit, ViewChild} from '@angular/core';
import {Component, OnInit, ViewChild, AfterViewInit} from '@angular/core';
import {
async,
ComponentFixture,
Expand Down Expand Up @@ -151,14 +151,14 @@ describe('MatTable', () => {
let dataSource: MatTableDataSource<TestData>;
let component: ArrayDataSourceMatTableApp;

beforeEach(() => {
beforeEach(fakeAsync(() => {
fixture = TestBed.createComponent(ArrayDataSourceMatTableApp);
fixture.detectChanges();

tableElement = fixture.nativeElement.querySelector('.mat-table');
component = fixture.componentInstance;
dataSource = fixture.componentInstance.dataSource;
});
}));

it('should create table and display data source contents', () => {
expectTableToMatchContent(tableElement, [
Expand Down Expand Up @@ -197,6 +197,33 @@ describe('MatTable', () => {
]);
});

it('should update the page index when switching to a smaller data set from a page',
fakeAsync(() => {
// Add 20 rows so we can switch pages.
for (let i = 0; i < 20; i++) {
component.underlyingDataSource.addData();
fixture.detectChanges();
tick();
fixture.detectChanges();
}

// Go to the last page.
fixture.componentInstance.paginator.lastPage();
fixture.detectChanges();

// Switch to a smaller data set.
dataSource.data = [{a: 'a_0', b: 'b_0', c: 'c_0'}];
fixture.detectChanges();
tick();
fixture.detectChanges();

expectTableToMatchContent(tableElement, [
['Column A', 'Column B', 'Column C'],
['a_0', 'b_0', 'c_0'],
['Footer A', 'Footer B', 'Footer C'],
]);
}));

it('should be able to filter the table contents', fakeAsync(() => {
// Change filter to a_1, should match one row
dataSource.filter = 'a_1';
Expand Down Expand Up @@ -650,7 +677,7 @@ class MatTableWithWhenRowApp {
<mat-paginator [pageSize]="5"></mat-paginator>
`
})
class ArrayDataSourceMatTableApp implements OnInit {
class ArrayDataSourceMatTableApp implements AfterViewInit {
underlyingDataSource = new FakeDataSource();
dataSource = new MatTableDataSource<TestData>();
columnsToRender = ['column_a', 'column_b', 'column_c'];
Expand All @@ -673,9 +700,9 @@ class ArrayDataSourceMatTableApp implements OnInit {
});
}

ngOnInit() {
this.dataSource!.sort = this.sort;
this.dataSource!.paginator = this.paginator;
ngAfterViewInit() {
this.dataSource.sort = this.sort;
this.dataSource.paginator = this.paginator;
}
}

Expand Down