diff --git a/src/cdk/table/table.spec.ts b/src/cdk/table/table.spec.ts
index d618b79523e6..4c3e3a326000 100644
--- a/src/cdk/table/table.spec.ts
+++ b/src/cdk/table/table.spec.ts
@@ -801,6 +801,25 @@ describe('CdkTable', () => {
]);
});
+ it('should replace only the row whose row definition changed', () => {
+ setupTableTestApp(WhenRowChangeDetectionCdkTableApp);
+ const initialRows = getRows(tableElement);
+
+ initialRows.forEach(row => expect(row.classList).toContain('default-row'));
+
+ component.showAlternate = true;
+ component.table.renderRows();
+ fixture.detectChanges();
+
+ const updatedRows = getRows(tableElement);
+ expect(updatedRows[0]).toBe(initialRows[0]);
+ expect(updatedRows[1]).not.toBe(initialRows[1]);
+ expect(updatedRows[2]).toBe(initialRows[2]);
+ expect(updatedRows[0].classList).toContain('default-row');
+ expect(updatedRows[1].classList).toContain('alternate-row');
+ expect(updatedRows[2].classList).toContain('default-row');
+ });
+
it('should error if there is row data that does not have a matching row template', fakeAsync(() => {
const whenRowWithoutDefaultFixture = TestBed.createComponent(
WhenRowWithoutDefaultCdkTableApp,
@@ -2405,6 +2424,31 @@ class WhenRowCdkTableApp {
}
}
+@Component({
+ template: `
+
+
+ {{row.a}}
+
+
+
+
+
+ `,
+ imports: [CdkTableModule],
+ changeDetection: ChangeDetectionStrategy.Eager,
+})
+class WhenRowChangeDetectionCdkTableApp {
+ dataSource = new FakeDataSource();
+ showAlternate = false;
+
+ @ViewChild(CdkTable) table!: CdkTable;
+
+ trackByIndex = (index: number, _item: TestData) => index;
+ isAlternateRow = (index: number, _item: TestData) => this.showAlternate && index === 1;
+}
+
@Component({
template: `
diff --git a/src/cdk/table/table.ts b/src/cdk/table/table.ts
index 0818d1ef981b..f09107661b44 100644
--- a/src/cdk/table/table.ts
+++ b/src/cdk/table/table.ts
@@ -423,6 +423,12 @@ export class CdkTable
*/
private _cachedRenderRowsMap = new Map, RenderRow[]>>();
+ /**
+ * Row definition used to create each rendered data-row view. Keying by the view keeps the
+ * association intact when the differ moves rows.
+ */
+ private _rowDefsByView = new WeakMap>, CdkRowDef>();
+
/** Whether the table is applied to a native ``. */
protected _isNativeHtmlTable: boolean;
@@ -750,20 +756,32 @@ export class CdkTable
(change: _ViewRepeaterItemChange, RowContext>) => {
if (change.operation === _ViewRepeaterOperation.INSERTED && change.context) {
this._renderCellTemplateForItem(change.record.item.rowDef, change.context);
+ const rowView = viewContainer.get(change.record.currentIndex!) as RowViewRef;
+ this._rowDefsByView.set(rowView, change.record.item.rowDef);
}
},
);
- // Update the meta context of a row's context data (index, count, first, last, ...)
- this._updateRowIndexContext();
-
// Update rows that did not get added/removed/moved but may have had their identity changed,
// e.g. if trackBy matched data on some property but the actual data reference changed.
changes.forEachIdentityChange((record: IterableChangeRecord>) => {
- const rowView = >viewContainer.get(record.currentIndex!);
- rowView.context.$implicit = record.item.data;
+ const currentIndex = record.currentIndex!;
+ const rowView = viewContainer.get(currentIndex) as RowViewRef;
+
+ if (this._rowDefsByView.get(rowView) !== record.item.rowDef) {
+ viewContainer.remove(currentIndex);
+ const newRowView = this._renderRow(this._rowOutlet, record.item.rowDef, currentIndex, {
+ $implicit: record.item.data,
+ });
+ this._rowDefsByView.set(newRowView, record.item.rowDef);
+ } else {
+ rowView.context.$implicit = record.item.data;
+ }
});
+ // Update the meta context of a row's context data (index, count, first, last, ...)
+ this._updateRowIndexContext();
+
this._updateNoDataRow();
this.contentChanged.next();