angular/components

CDK table inappropriately rerenders table header cells when there are multiple headers

anthonyrgreen opened this issue · 0 comments

Which @angular/* package(s) are the source of the bug?

Don't known / other

Is this a regression?

No

Description

Tl;dr: See a minimal repro with root-cause, explanation, and potential fix here: https://stackblitz.com/edit/angular-material-table-multiple-header-rows-nj2ljf?file=app%2Ftable-basic-example.html

If I create a mat-table, and give it multiple header rows like so:

    <tr mat-header-row *matHeaderRowDef="displayedHeaderColumns"></tr>
    <tr mat-header-row *matHeaderRowDef="displayedSubheaderColumns"></tr>

Then, when I append a column to the inner values of both displayedHeaderColumns and displayedHeaderColumns, the cdk-table change-detection code renders the table into a 'dirty' state. The result of this is that, on the first dirty-check of the table, the table headers rerender, destroying and recreating all the <th mat-header-cell> elements and preempting any interaction handlers in progress. This prevents e.g. button clicks from being registered, and, in my app, stops a menu from opening from the table header.

This phenomenon only occurs on the FIRST click post-column-add, and only if you include the 2nd (sub)header in your table definition, i.e. commenting out <tr mat-header-row *matHeaderRowDef="displayedSubheaderColumns"></tr> mitigates the bug.

Possible root cause of the bug

I think this is happening because Angular somehow rerenders the subheader as a result of dirty-checking the header, but does not update its internal records of the subheader's state in the process. Clicking the button causes the table to re-check its subheader's columns, at which point its differ discovers it hasn't officially added the newest column to the list (although it is already visually displaying the newest column).

This bug disappears if I modify CdkTable._renderUpdatedColumns in the following manner at angular/components/src/cdk/table/table.ts:1087:

Original code:

  const columnsDiffReducer = (acc: boolean, def: BaseRowDef) => 
    acc || !!def.getColumnsDiff();

Fixed code:

  const columnsDiffReducer = (acc: boolean, def: BaseRowDef) => {
    const diff = def.getColumnsDiff();
    return acc || !!diff;
  };

I think that the lazy computation of def.getColumnsDiff() is causing the subheader state not to get updated.

Please provide a link to a minimal reproduction of the bug

https://stackblitz.com/edit/angular-material-table-multiple-header-rows-nj2ljf?file=app%2Ftable-basic-example.html

Please provide the exception or error you saw

There is no exception. The error is that, when the user clicks their first button on a table header, all the table headers rerender, destroying and recreating each <th mat-header-cell> element and preempting any interaction handlers in progress. This prevents e.g. button clicks from being registered, and, in my app, stops a menu from opening from the table header.

Please provide the environment you discovered this bug in (run ng version)

{
"name": "xokrrnnaegq.angular",
"version": "0.0.0",
"private": true,
"dependencies": {
"rxjs": "6.4.0",
"moment": "2.24.0",
"core-js": "2.6.4",
"zone.js": "0.8.29",
"hammerjs": "2.0.8",
"@angular/cdk": "7.3.1",
"@angular/core": "7.2.4",
"@angular/http": "7.2.4",
"@angular/forms": "7.2.4",
"@angular/common": "7.2.4",
"@angular/router": "7.2.4",
"@angular/compiler": "7.2.4",
"@angular/material": "7.3.1",
"web-animations-js": "2.3.1",
"@angular/animations": "7.2.4",
"@angular/platform-browser": "7.2.4",
"angular-in-memory-web-api": "0.5.4",
"@angular/material-moment-adapter": "7.3.1",
"@angular/platform-browser-dynamic": "7.2.4",
"@ngrx/store": "^18.1.0"
},
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.10.0",
"@angular/cli": "~7.0.2",
"@angular/compiler-cli": "~7.0.0",
"@angular/language-service": "~7.0.0",
"@types/node": "~8.9.4",
"@types/jasmine": "~2.8.8",
"@types/jasminewd2": "~2.0.3",
"codelyzer": "~4.5.0",
"jasmine-core": "~2.99.1",
"jasmine-spec-reporter": "~4.2.1",
"karma": "~3.0.0",
"karma-chrome-launcher": "~2.2.0",
"karma-coverage-istanbul-reporter": "~2.0.1",
"karma-jasmine": "~1.1.2",
"karma-jasmine-html-reporter": "^0.2.2",
"protractor": "~5.4.0",
"ts-node": "~7.0.0",
"tslint": "~5.11.0",
"typescript": "~3.1.1"
}
}

Anything else?

No response