Skip to content

bug(cdk-table): Row differ (trackBy) does not detect row template changes #20717

Closed

Description

@shlomiassaf

The CDK Table will detect changes in rows based on a differ which is based on the trackBy input function.

this._dataDiffer = this._differs.find([]).create((_i: number, dataRow: RenderRow<T>) => {
return this.trackBy ? this.trackBy(dataRow.dataIndex, dataRow.data) : dataRow;
});

This is fine when actual data changes but it does not handle a scenario where the row template has changed but the data has not changed, which is an issue especially when working with virtual scroll.

Since the trackBy handler does not get access to the rowDef, only to the index and raw row data, it is not possible to workaround.

Even if there is access to the rowDef, trackBy is not a predicate so it will be hard to return something logical there, trackBy is for identity, this one needs to happen outside of the trackBy

In some cases, one might write an alternate trackBy function but it will not do for all scenarios. Moreover, the trackBy function must be dead simple and fast, if the view port is large, showing enough rows with enough columns it becomes a bottleneck.

Usually, most performant way to do the trackBy is just return the index.

However, if the rowDef has changed, it will not be possible to detect it.

Once diff has been calculated, the call to renderRows will ignore the change in the row template definition

if (this._viewRepeater) {
this._viewRepeater.applyChanges(
changes,
viewContainer,
(record: IterableChangeRecord<RenderRow<T>>,
_adjustedPreviousIndex: number|null,
currentIndex: number|null) => this._getEmbeddedViewArgs(record.item, currentIndex!),
(record) => record.item.data,
(change: _ViewRepeaterItemChange<RenderRow<T>, RowContext<T>>) => {
if (change.operation === _ViewRepeaterOperation.INSERTED && change.context) {
this._renderCellTemplateForItem(change.record.item.rowDef, change.context);
}
});

It will not call _renderCellTemplateForItem leaving it with the old template cached in the table.

Reproduction

https://stackblitz.com/edit/angular-qvzms7?file=src%2Fapp%2Ftable-flex-basic-example.ts

Basically, we need to have multiple row definitions (CdkRowDef) but multiTemplateDataRows is set to false.
We define 2 row templates with when predicates, where the 2 predicates are opposite, if one is on the other is off and vice versa.

Using a simple button to toggle a boolean flag we switch between the 2 predicates.

If we use a simple button it will not work, we switch the flag but it does not change the rendered template for the row.

Using the ChangeDetectorRef to run manual CD will not work as well because the table handles it's own diff mechanism when it comes to row diffing.

The only way (I found so far...) to force the proper row template to render is calling _forceRenderDataRows(); which is private. The will force render the entire dataset, using the new row template. This is bad of course, as it's using a private method and it will force render ALL rows, while in real scenarios one will only want some to re-render.

Expected Behavior

What behavior were you expecting to see?
Being able to change the row definition template and have the CDK Table autodetect and render it.

Actual Behavior

What behavior did you actually see?
CDK Table fail to detect changes in the row definition template.

Environment

  • Angular: 10+
  • CDK/Material: 10+

Activity

  1. shlomiassaf commented on Oct 4, 2020

    @shlomiassaf
    ContributorAuthor

    One idea towards a fix is to use the call to _getAllRenderRows() just before calculating the diff.

    this._renderRows = this._getAllRenderRows();
    const changes = this._dataDiffer.diff(this._renderRows);

    _getAllRenderRows() iterates over all rows to be rendered and extracts their rowDef template (PERFECT!) which can be compared to the actual rendered row def template and if different, remove it from returned results (this._renderRows) so it will hit in the diff.

    This comparison is super fast as we compare references of the rowDef

  2. added
    help wantedLabel noting an issue which the team is looking for contribution from the community to fix
    P3An issue that is relevant to core functions, but does not impede progress. Important, but not urgent
    and removed
    needs triageThis issue needs to be triaged by the team
    on Oct 6, 2020
  3. jelbourn commented on Oct 6, 2020

    @jelbourn
    Contributor

    @shlomiassaf yeah, it does sound like the table should just re-grab all of the rowDefs before running the differ.

  4. added 3 commits that reference this issue on Oct 11, 2020
    857e95d
    f1254d9
    dbec01f
  5. viniciusschuelter commented on Sep 13, 2022

    @viniciusschuelter

    This issue should not be labeled "has pr" @jelbourn. Btw, what happened to pr #20765?

  6. jelbourn commented on Sep 16, 2022

    @jelbourn
    Contributor

    Seems like that PR stalled out due to failures inside Google and hasn't been enough of a priority to investigate further.

  7. angular-automatic-lock-bot commented on Oct 20, 2026

    @angular-automatic-lock-bot

    This issue has been automatically locked due to inactivity.
    Please file a new issue if you are encountering a similar or related problem.

    Read more about our automatic conversation locking policy.

    This action has been performed automatically by a bot.

  8. locked and limited conversation to collaborators on Oct 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3An issue that is relevant to core functions, but does not impede progress. Important, but not urgentarea: cdk/tablehelp wantedLabel noting an issue which the team is looking for contribution from the community to fix

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions