Skip to content

Commit

Permalink
fix(table): not re-rendering when switching to a smaller set of data …
Browse files Browse the repository at this point in the history
…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 angular#12586).

Fixes angular#14010.
  • Loading branch information
crisbeto committed Dec 28, 2018
1 parent d22f48c commit 07849e4
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 15 deletions.
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;

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
39 changes: 32 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,31 @@ 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();
}

// 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 +675,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 +698,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

0 comments on commit 07849e4

Please sign in to comment.