Skip to content

Commit f806286

Browse files
andrewseguinmmalerba
authored andcommitted
fix(table): update implicit when using trackby (#7893)
* fix(table): update implicit when using trackby * imports * syntax change
1 parent 0ea4370 commit f806286

File tree

2 files changed

+47
-13
lines changed

2 files changed

+47
-13
lines changed

src/cdk/table/table.spec.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,24 @@ describe('CdkTable', () => {
391391
expect(changedRows[1].getAttribute('initialIndex')).toBe('1');
392392
expect(changedRows[2].getAttribute('initialIndex')).toBe(null);
393393
});
394+
395+
it('should change row implicit data even when trackBy finds no changes', () => {
396+
createTestComponentWithTrackyByTable('index');
397+
const firstRow = getRows(tableElement)[0];
398+
expect(firstRow.textContent!.trim()).toBe('a_1 b_1');
399+
expect(firstRow.getAttribute('initialIndex')).toBe('0');
400+
mutateData();
401+
402+
// Change each item reference to show that the trackby is checking the index.
403+
// Otherwise this would cause them all to be removed/added.
404+
trackByComponent.dataSource.data = trackByComponent.dataSource.data
405+
.map(item => ({a: item.a, b: item.b, c: item.c}));
406+
407+
// Expect the rows were given the right implicit data even though the rows were not moved.
408+
trackByFixture.detectChanges();
409+
expect(firstRow.textContent!.trim()).toBe('a_2 b_2');
410+
expect(firstRow.getAttribute('initialIndex')).toBe('0');
411+
});
394412
});
395413

396414
it('should match the right table content with dynamic data', () => {

src/cdk/table/table.ts

Lines changed: 29 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ import {Subscription} from 'rxjs/Subscription';
3737
import {Subject} from 'rxjs/Subject';
3838
import {CdkCellDef, CdkColumnDef, CdkHeaderCellDef} from './cell';
3939
import {
40-
getTableDuplicateColumnNameError, getTableMissingMatchingRowDefError,
40+
getTableDuplicateColumnNameError,
41+
getTableMissingMatchingRowDefError,
4142
getTableMultipleDefaultRowDefsError,
4243
getTableUnknownColumnError
4344
} from './table-errors';
@@ -68,6 +69,12 @@ export const CDK_TABLE_TEMPLATE = `
6869
<ng-container headerRowPlaceholder></ng-container>
6970
<ng-container rowPlaceholder></ng-container>`;
7071

72+
/**
73+
* Class used to conveniently type the embedded view ref for rows with a context.
74+
* @docs-private
75+
*/
76+
abstract class RowViewRef<T> extends EmbeddedViewRef<CdkCellOutletRowContext<T>> { }
77+
7178
/**
7279
* A data table that connects with a data source to retrieve data of type `T` and renders
7380
* a header row and data rows. Updates the rows when new data is provided by the data source.
@@ -292,25 +299,36 @@ export class CdkTable<T> implements CollectionViewer {
292299
this._changeDetectorRef.markForCheck();
293300
}
294301

295-
/** Check for changes made in the data and render each change (row added/removed/moved). */
302+
/**
303+
* Check for changes made in the data and render each change (row added/removed/moved) and update
304+
* row contexts.
305+
*/
296306
private _renderRowChanges() {
297307
const changes = this._dataDiffer.diff(this._data);
298308
if (!changes) { return; }
299309

300310
const viewContainer = this._rowPlaceholder.viewContainer;
301311
changes.forEachOperation(
302-
(item: IterableChangeRecord<any>, adjustedPreviousIndex: number, currentIndex: number) => {
303-
if (item.previousIndex == null) {
304-
this._insertRow(this._data[currentIndex], currentIndex);
312+
(record: IterableChangeRecord<T>, adjustedPreviousIndex: number, currentIndex: number) => {
313+
if (record.previousIndex == null) {
314+
this._insertRow(record.item, currentIndex);
305315
} else if (currentIndex == null) {
306316
viewContainer.remove(adjustedPreviousIndex);
307317
} else {
308-
const view = viewContainer.get(adjustedPreviousIndex);
318+
const view = <RowViewRef<T>>viewContainer.get(adjustedPreviousIndex);
309319
viewContainer.move(view!, currentIndex);
310320
}
311321
});
312322

313-
this._updateRowContext();
323+
// Update the meta context of a row's context data (index, count, first, last, ...)
324+
this._updateRowIndexContext();
325+
326+
// Update rows that did not get added/removed/moved but may have had their identity changed,
327+
// e.g. if trackBy matched data on some property but the actual data reference changed.
328+
changes.forEachIdentityChange((record: IterableChangeRecord<T>) => {
329+
const rowView = <RowViewRef<T>>viewContainer.get(record.currentIndex!);
330+
rowView.context.$implicit = record.item;
331+
});
314332
}
315333

316334
/**
@@ -353,14 +371,13 @@ export class CdkTable<T> implements CollectionViewer {
353371
}
354372

355373
/**
356-
* Updates the context for each row to reflect any data changes that may have caused
357-
* rows to be added, removed, or moved. The view container contains the same context
358-
* that was provided to each of its cells.
374+
* Updates the index-related context for each row to reflect any changes in the index of the rows,
375+
* e.g. first/last/even/odd.
359376
*/
360-
private _updateRowContext() {
377+
private _updateRowIndexContext() {
361378
const viewContainer = this._rowPlaceholder.viewContainer;
362379
for (let index = 0, count = viewContainer.length; index < count; index++) {
363-
const viewRef = viewContainer.get(index) as EmbeddedViewRef<CdkCellOutletRowContext<T>>;
380+
const viewRef = viewContainer.get(index) as RowViewRef<T>;
364381
viewRef.context.index = index;
365382
viewRef.context.count = count;
366383
viewRef.context.first = index === 0;
@@ -404,4 +421,3 @@ export class CdkTable<T> implements CollectionViewer {
404421
});
405422
}
406423
}
407-

0 commit comments

Comments
 (0)