From a763fe96da71c617c7cc2022bbe9d0f1605974f0 Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Thu, 17 Apr 2025 10:43:42 -0600 Subject: [PATCH] feat(material/table): add API to force datasource to update --- src/material/table/table-data-source.ts | 15 ++++++++- src/material/table/table.spec.ts | 44 ++++++++++++++++++++----- 2 files changed, 49 insertions(+), 10 deletions(-) diff --git a/src/material/table/table-data-source.ts b/src/material/table/table-data-source.ts index c19e8e1e921a..e2115ca1e2b2 100644 --- a/src/material/table/table-data-source.ts +++ b/src/material/table/table-data-source.ts @@ -53,6 +53,9 @@ export class MatTableDataSource extend /** Used to react to internal changes of the paginator that are made by the data source itself. */ private readonly _internalPageChanges = new Subject(); + /** Stream that will force the data source to emit new data. */ + private readonly _forceEmit = new BehaviorSubject(true); + /** * Subscription to the changes that should trigger an update to the table's rendered rows, such * as filtering, sorting, pagination, or base data changes. @@ -243,6 +246,16 @@ export class MatTableDataSource extend this._updateChangeSubscription(); } + /** + * Forces the data source to emit data to the table based on the current state of the sort + * and paginator. This may be necessary if the state of the paginator or sort components have + * been changed programmatically through their Inputs, which do not emit Output events that + * would otherwise notify a change. + */ + forceEmit(): void { + this._forceEmit.next(true); + } + /** * Subscribe to changes that should trigger an update to the table's rendered rows. When the * changes occur, process the current state of the filter, sort, and pagination along with @@ -267,7 +280,7 @@ export class MatTableDataSource extend : 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]).pipe( + const filteredData = combineLatest([dataStream, this._filter, this._forceEmit]).pipe( map(([data]) => this._filterData(data)), ); // Watch for filtered data or sort changes to provide an ordered set of data. diff --git a/src/material/table/table.spec.ts b/src/material/table/table.spec.ts index 20debc1c261c..602ce09ccfcd 100644 --- a/src/material/table/table.spec.ts +++ b/src/material/table/table.spec.ts @@ -542,15 +542,26 @@ describe('MatTable', () => { for (let i = 0; i < 100; i++) { component.underlyingDataSource.addData(); } - fixture.detectChanges(); - flushMicrotasks(); // Resolve promise that updates paginator's length - expectTableToMatchContent(tableElement, [ - ['Column A', 'Column B', 'Column C'], + const firstPage = [ ['a_1', 'b_1', 'c_1'], ['a_2', 'b_2', 'c_2'], ['a_3', 'b_3', 'c_3'], ['a_4', 'b_4', 'c_4'], ['a_5', 'b_5', 'c_5'], + ]; + const secondPage = [ + ['a_6', 'b_6', 'c_6'], + ['a_7', 'b_7', 'c_7'], + ['a_8', 'b_8', 'c_8'], + ['a_9', 'b_9', 'c_9'], + ['a_10', 'b_10', 'c_10'], + ]; + + fixture.detectChanges(); + flushMicrotasks(); // Resolve promise that updates paginator's length + expectTableToMatchContent(tableElement, [ + ['Column A', 'Column B', 'Column C'], + ...firstPage, ['Footer A', 'Footer B', 'Footer C'], ]); @@ -559,11 +570,26 @@ describe('MatTable', () => { fixture.detectChanges(); expectTableToMatchContent(tableElement, [ ['Column A', 'Column B', 'Column C'], - ['a_6', 'b_6', 'c_6'], - ['a_7', 'b_7', 'c_7'], - ['a_8', 'b_8', 'c_8'], - ['a_9', 'b_9', 'c_9'], - ['a_10', 'b_10', 'c_10'], + ...secondPage, + ['Footer A', 'Footer B', 'Footer C'], + ]); + + component.paginator.pageIndex = 0; + fixture.detectChanges(); + // Expect no changes from before since the data source has not been told to check and + // the paginator did not send an Output based on the change. + expectTableToMatchContent(tableElement, [ + ['Column A', 'Column B', 'Column C'], + ...secondPage, + ['Footer A', 'Footer B', 'Footer C'], + ]); + + // Use data source's `forceEmit` to update data based on the latest state in the paginator. + component.dataSource.forceEmit(); + fixture.detectChanges(); + expectTableToMatchContent(tableElement, [ + ['Column A', 'Column B', 'Column C'], + ...firstPage, ['Footer A', 'Footer B', 'Footer C'], ]); }));