Skip to content

Commit 5b45df3

Browse files
authored
fix(cdk/table): ensure CdkTable updates view with OnPush and trackBy (angular#31451)
When CdkTable is used with OnPush change detection and a `trackBy` function, changes to the `dataSource` might not trigger a view update if the `trackBy` function indicates that the rows themselves haven't changed (even if their internal data has). This can leave the data-bound elements within the table rows displaying outdated information. This commit adds `markForCheck()` calls within the `_renderRows` method of `CdkTable`. These calls ensure that when the table's data is updated (even without full row re-creation due to `trackBy`), the component is explicitly marked for change detection. This triggers a re-evaluation of the bindings for all visible rows, ensuring the view remains consistent with the underlying data. Fixes angular#24483
1 parent c396821 commit 5b45df3

File tree

2 files changed

+45
-1
lines changed

2 files changed

+45
-1
lines changed

src/cdk/table/table.spec.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
Type,
1414
ViewChild,
1515
inject,
16+
TrackByFunction,
1617
} from '@angular/core';
1718
import {By} from '@angular/platform-browser';
1819
import {ComponentFixture, TestBed, fakeAsync, flush, waitForAsync} from '@angular/core/testing';
@@ -1995,6 +1996,35 @@ describe('CdkTable', () => {
19951996
expect(noDataRow).toBeTruthy();
19961997
expect(noDataRow.getAttribute('colspan')).toEqual('3');
19971998
});
1999+
2000+
it('should properly update table content when data changes in OnPush component with track by instance', () => {
2001+
setupTableTestApp(WrapNativeTrackByHtmlTableAppOnPush);
2002+
2003+
const data = component.dataSource.data;
2004+
2005+
expectTableToMatchContent(tableElement, [
2006+
['Column A', 'Column B', 'Column C'],
2007+
[data[0].a, data[0].b, data[0].c],
2008+
[data[1].a, data[1].b, data[1].c],
2009+
[data[2].a, data[2].b, data[2].c],
2010+
]);
2011+
2012+
component.dataSource.data = component.dataSource.data.map((data: TestData) => ({
2013+
...data,
2014+
b: `${data.b}-updated`,
2015+
}));
2016+
2017+
fixture.detectChanges();
2018+
2019+
const newData = component.dataSource.data;
2020+
2021+
expectTableToMatchContent(tableElement, [
2022+
['Column A', 'Column B', 'Column C'],
2023+
[newData[0].a, newData[0].b, newData[0].c],
2024+
[newData[1].a, newData[1].b, newData[1].c],
2025+
[newData[2].a, newData[2].b, newData[2].c],
2026+
]);
2027+
});
19982028
});
19992029

20002030
interface TestData {
@@ -3135,7 +3165,7 @@ class TableWithIndirectDescendantDefs {
31353165
@Component({
31363166
selector: 'cdk-table-change-detection-on-push',
31373167
template: `
3138-
<table cdk-table [dataSource]="dataSource">
3168+
<table cdk-table [dataSource]="dataSource" [trackBy]="trackBy">
31393169
<ng-container cdkColumnDef="column_a">
31403170
<th cdk-header-cell *cdkHeaderCellDef> Column A</th>
31413171
<td cdk-cell *cdkCellDef="let row"> {{row.a}}</td>
@@ -3163,6 +3193,7 @@ class TableWithIndirectDescendantDefs {
31633193
})
31643194
class NativeHtmlTableAppOnPush {
31653195
@Input() dataSource: Observable<TestData[]> | null = null;
3196+
@Input() trackBy: TrackByFunction<TestData> | undefined;
31663197
columnsToRender = ['column_a', 'column_b', 'column_c'];
31673198
}
31683199

@@ -3176,6 +3207,17 @@ class WrapNativeHtmlTableAppOnPush {
31763207
dataSource: FakeDataSource = new FakeDataSource();
31773208
}
31783209

3210+
@Component({
3211+
template: `
3212+
<cdk-table-change-detection-on-push [dataSource]="dataSource" [trackBy]="trackBy"></cdk-table-change-detection-on-push>
3213+
`,
3214+
imports: [NativeHtmlTableAppOnPush],
3215+
})
3216+
class WrapNativeTrackByHtmlTableAppOnPush {
3217+
dataSource: FakeDataSource = new FakeDataSource();
3218+
trackBy: TrackByFunction<TestData> = (index: number, data: TestData) => data.a;
3219+
}
3220+
31793221
function getElements(element: Element, query: string): HTMLElement[] {
31803222
return [].slice.call(element.querySelectorAll(query));
31813223
}

src/cdk/table/table.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -682,6 +682,7 @@ export class CdkTable<T>
682682
if (!changes) {
683683
this._updateNoDataRow();
684684
this.contentChanged.next();
685+
this._changeDetectorRef.markForCheck();
685686
return;
686687
}
687688
const viewContainer = this._rowOutlet.viewContainer;
@@ -716,6 +717,7 @@ export class CdkTable<T>
716717

717718
this.contentChanged.next();
718719
this.updateStickyColumnStyles();
720+
this._changeDetectorRef.markForCheck();
719721
}
720722

721723
/** Adds a column definition that was not included as part of the content children. */

0 commit comments

Comments
 (0)