Components: bug(table): Header cell content generated by ngFor is not updated

Created on 10 Nov 2017  路  2Comments  路  Source: angular/components

Bug, feature request, or proposal:

As the title states, it seems like the mat-header-cell's interpolated content isn't recalculated when properties change.

What is the expected behavior?

That the id0 column generated by the *ngFor would behave like the id column and dynamically update when the week array changes.

For comparison, the <span> within the chevron buttons is also generated using an *ngFor and updates without issue.

What is the current behavior?

The id0 column's header content is not re-evaluated when the week array changes.

What are the steps to reproduce?

https://stackblitz.com/edit/material2-rc0-vb1rjf

Use the chevron buttons to navigate forward and backward. The date of the second id column doesn't change, while the first does.

What is the use-case or motivation for changing an existing behavior?

My use case is that I'd like to have a table based around a week-view, where users can navigate forward and backward. It'd be nice if the mat table headers could dynamically update.

Which versions of Angular, Material, OS, TypeScript, browsers are affected?

Latest versions of Angular, Material, Typescript, Windows 10, and Chrome.

Is there anything else we should know?

P3

Most helpful comment

_tl;dr: Add an index trackBy function to your ngFor_

Lots going on here in the template, I'll try to distill it a bit to show what the issue is.

ngFor is creating a set of columns (in this case, just one column). The table picks up this column id0 and grabs the template to stamp out into the DOM. This template will render changes according to your AppComponent's interpolation checks since it lives in its view.

When you change week, you are telling the ngFor to destroy the id0 column template and create a new one. This is because it assumes that due to a reference change (different week array) that the template is no longer valid and needs to be remade.

The table is told about the new column id0 and so it does register this new template. However, the mat-header-row was told to render the columns data, id, and id0, which it is already doing. As far as it knows, it doesn't need to change and so the table does not re-render with the new template constructed by the ngFor.

So now we are in a state where the table has stamped a particular set of content to the DOM that has been orphaned by its view. The table never re-stamped out the new ngFor column template, which would be updated by the AppComponent view.

So we have two options - (1) tell the table that it should re-stamp out the header cells by making a change to the columns we want to render or (2) do not destroy/build new column definitions when the week array changes.

Since option (1) means we have to actually change the columns (table will see there's no diff), let's go with option (2).

How do we tell ngFor that the template doesn't need to be changed if it already build a template for each array index? By adding a trackBy function that tells the ngFor to identify templates based on their index rather than by object reference.

In the AppComponent, create a TrackByFn that returns the array index:

trackByIndex = i => i;

If the syntax looks odd, this is the same as

trackByIndex = function(i) { return i; }

Now, tell the ngFor about this trackBy:

*ngFor="let d of week; let i = index; trackBy: trackByIndex"

And voila, now the template will be provided to the table and not re-created. Its content will be updated when the view's change detection is run and the table will be consistent with showing this template.

All 2 comments

_tl;dr: Add an index trackBy function to your ngFor_

Lots going on here in the template, I'll try to distill it a bit to show what the issue is.

ngFor is creating a set of columns (in this case, just one column). The table picks up this column id0 and grabs the template to stamp out into the DOM. This template will render changes according to your AppComponent's interpolation checks since it lives in its view.

When you change week, you are telling the ngFor to destroy the id0 column template and create a new one. This is because it assumes that due to a reference change (different week array) that the template is no longer valid and needs to be remade.

The table is told about the new column id0 and so it does register this new template. However, the mat-header-row was told to render the columns data, id, and id0, which it is already doing. As far as it knows, it doesn't need to change and so the table does not re-render with the new template constructed by the ngFor.

So now we are in a state where the table has stamped a particular set of content to the DOM that has been orphaned by its view. The table never re-stamped out the new ngFor column template, which would be updated by the AppComponent view.

So we have two options - (1) tell the table that it should re-stamp out the header cells by making a change to the columns we want to render or (2) do not destroy/build new column definitions when the week array changes.

Since option (1) means we have to actually change the columns (table will see there's no diff), let's go with option (2).

How do we tell ngFor that the template doesn't need to be changed if it already build a template for each array index? By adding a trackBy function that tells the ngFor to identify templates based on their index rather than by object reference.

In the AppComponent, create a TrackByFn that returns the array index:

trackByIndex = i => i;

If the syntax looks odd, this is the same as

trackByIndex = function(i) { return i; }

Now, tell the ngFor about this trackBy:

*ngFor="let d of week; let i = index; trackBy: trackByIndex"

And voila, now the template will be provided to the table and not re-created. Its content will be updated when the view's change detection is run and the table will be consistent with showing this template.

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._

Was this page helpful?
0 / 5 - 0 ratings

Related issues

LoganDupont picture LoganDupont  路  3Comments

jelbourn picture jelbourn  路  3Comments

savaryt picture savaryt  路  3Comments

theunreal picture theunreal  路  3Comments

xtianus79 picture xtianus79  路  3Comments