Skip to content

Commit 9327262

Browse files
committed
fix(table): errors when rendering table with sticky elements on the server
Fixes a handful of errors that were being thrown by `mat-table` when it has sticky rows and when it's being used with the native `table` elements. Fixes #12094.
1 parent e23709f commit 9327262

File tree

6 files changed

+55
-9
lines changed

6 files changed

+55
-9
lines changed

src/cdk/table/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ ng_module(
1111
"//src/cdk/bidi",
1212
"//src/cdk/collections",
1313
"//src/cdk/coercion",
14+
"//src/cdk/platform",
1415
"@rxjs",
1516
],
1617
tsconfig = "//src/cdk:tsconfig-build.json",

src/cdk/table/sticky-styler.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,12 @@ export class StickyStyler {
3232
* sticky positioning applied.
3333
* @param direction The directionality context of the table (ltr/rtl); affects column positioning
3434
* by reversing left/right positions.
35+
* @param _isBrowser Whether the table is currently being rendered on the server or the client.
3536
*/
3637
constructor(private isNativeHtmlTable: boolean,
3738
private stickCellCss: string,
38-
public direction: Direction) { }
39+
public direction: Direction,
40+
private _isBrowser = true) { }
3941

4042
/**
4143
* Clears the sticky positioning styles from the row and its cells by resetting the `position`
@@ -66,7 +68,7 @@ export class StickyStyler {
6668
rows: HTMLElement[], stickyStartStates: boolean[], stickyEndStates: boolean[]) {
6769
const hasStickyColumns =
6870
stickyStartStates.some(state => state) || stickyEndStates.some(state => state);
69-
if (!rows.length || !hasStickyColumns) {
71+
if (!rows.length || !hasStickyColumns || !this._isBrowser) {
7072
return;
7173
}
7274

@@ -104,6 +106,11 @@ export class StickyStyler {
104106
*
105107
*/
106108
stickRows(rowsToStick: HTMLElement[], stickyStates: boolean[], position: 'top' | 'bottom') {
109+
// Since we can't measure the rows on the server, we can't stick the rows properly.
110+
if (!this._isBrowser) {
111+
return;
112+
}
113+
107114
// If positioning the rows to the bottom, reverse their order when evaluating the sticky
108115
// position such that the last row stuck will be "bottom: 0px" and so on.
109116
const rows = position === 'bottom' ? rowsToStick.reverse() : rowsToStick;

src/cdk/table/table.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ import {
3131
ViewChild,
3232
ViewContainerRef,
3333
ViewEncapsulation,
34+
Inject,
3435
} from '@angular/core';
36+
import {DOCUMENT} from '@angular/common';
3537
import {BehaviorSubject, Observable, of as observableOf, Subject, Subscription} from 'rxjs';
3638
import {takeUntil} from 'rxjs/operators';
3739
import {CdkColumnDef} from './cell';
@@ -55,6 +57,7 @@ import {
5557
import {coerceBooleanProperty} from '@angular/cdk/coercion';
5658
import {StickyStyler} from './sticky-styler';
5759
import {Direction, Directionality} from '@angular/cdk/bidi';
60+
import {Platform} from '@angular/cdk/platform';
5861

5962
/** Interface used to provide an outlet for rows to be inserted into. */
6063
export interface RowOutlet {
@@ -148,6 +151,8 @@ export interface RenderRow<T> {
148151
changeDetection: ChangeDetectionStrategy.OnPush,
149152
})
150153
export class CdkTable<T> implements AfterContentChecked, CollectionViewer, OnDestroy, OnInit {
154+
private _document: Document;
155+
151156
/** Latest data provided by the data source. */
152157
protected _data: T[];
153158

@@ -359,11 +364,19 @@ export class CdkTable<T> implements AfterContentChecked, CollectionViewer, OnDes
359364
protected readonly _changeDetectorRef: ChangeDetectorRef,
360365
protected readonly _elementRef: ElementRef,
361366
@Attribute('role') role: string,
362-
@Optional() protected readonly _dir: Directionality) {
367+
@Optional() protected readonly _dir: Directionality,
368+
/**
369+
* @deprecated
370+
* @deletion-target 8.0.0 `_document` and `_platform` to
371+
* be made into a required parameters.
372+
*/
373+
@Inject(DOCUMENT) _document?: any,
374+
private _platform?: Platform) {
363375
if (!role) {
364376
this._elementRef.nativeElement.setAttribute('role', 'grid');
365377
}
366378

379+
this._document = _document;
367380
this._isNativeHtmlTable = this._elementRef.nativeElement.nodeName === 'TABLE';
368381
}
369382

@@ -947,7 +960,9 @@ export class CdkTable<T> implements AfterContentChecked, CollectionViewer, OnDes
947960
];
948961

949962
for (const section of sections) {
950-
const element = document.createElement(section.tag);
963+
// @deletion-target 8.0.0 remove the `|| document` once the `_document` is a required param.
964+
const documentRef = this._document || document;
965+
const element = documentRef.createElement(section.tag);
951966
element.appendChild(section.outlet.elementRef.nativeElement);
952967
this._elementRef.nativeElement.appendChild(element);
953968
}
@@ -999,7 +1014,9 @@ export class CdkTable<T> implements AfterContentChecked, CollectionViewer, OnDes
9991014
*/
10001015
private _setupStickyStyler() {
10011016
const direction: Direction = this._dir ? this._dir.value : 'ltr';
1002-
this._stickyStyler = new StickyStyler(this._isNativeHtmlTable, this.stickyCssClass, direction);
1017+
this._stickyStyler = new StickyStyler(this._isNativeHtmlTable,
1018+
// @deletion-target 8.0.0 remove the null check for `this._platform`.
1019+
this.stickyCssClass, direction, this._platform ? this._platform.isBrowser : true);
10031020
(this._dir ? this._dir.change : observableOf<Direction>())
10041021
.pipe(takeUntil(this._onDestroy))
10051022
.subscribe(value => {
@@ -1010,6 +1027,6 @@ export class CdkTable<T> implements AfterContentChecked, CollectionViewer, OnDes
10101027
}
10111028

10121029
/** Utility function that gets a merged list of the entries in a QueryList and values of a Set. */
1013-
function mergeQueryListAndSet<T>(queryList: QueryList<T>, set: Set<T>): T[] {
1030+
function mergeQueryListAndSet<T>(queryList: QueryList<T>, set: Set<T>): T[] {
10141031
return queryList.toArray().concat(Array.from(set));
10151032
}

src/lib/table/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ ng_module(
1414
"//src/lib/paginator",
1515
"//src/lib/sort",
1616
"//src/cdk/table",
17+
"//src/cdk/platform",
1718
"@rxjs",
1819
] + glob(["**/*.html"]),
1920
tsconfig = "//src/lib:tsconfig-build.json",

src/lib/table/table.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,13 @@ import {
1414
ElementRef,
1515
IterableDiffers,
1616
Optional,
17-
ViewEncapsulation
17+
ViewEncapsulation,
18+
Inject
1819
} from '@angular/core';
20+
import {DOCUMENT} from '@angular/common';
1921
import {CDK_TABLE_TEMPLATE, CdkTable} from '@angular/cdk/table';
2022
import {Directionality} from '@angular/cdk/bidi';
23+
import {Platform} from '@angular/cdk/platform';
2124

2225
/**
2326
* Wrapper for the CdkTable with Material design styles.
@@ -47,7 +50,9 @@ export class MatTable<T> extends CdkTable<T> {
4750
protected _changeDetectorRef: ChangeDetectorRef,
4851
protected _elementRef: ElementRef,
4952
@Attribute('role') role: string,
50-
@Optional() protected readonly _dir: Directionality) {
51-
super(_differs, _changeDetectorRef, _elementRef, role, _dir);
53+
@Optional() protected readonly _dir: Directionality,
54+
@Inject(DOCUMENT) _document?: any,
55+
platform?: Platform) {
56+
super(_differs, _changeDetectorRef, _elementRef, role, _dir, _document, platform);
5257
}
5358
}

src/universal-app/kitchen-sink/kitchen-sink.html

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,21 @@ <h2>Material Table</h2>
303303
<mat-row *cdkRowDef="let row; columns: tableColumns;"></mat-row>
304304
</mat-table>
305305

306+
<h2>Native table with sticky header and footer</h2>
307+
308+
<table mat-table [dataSource]="tableDataSource">
309+
<ng-container matColumnDef="userId">
310+
<th mat-header-cell *matHeaderCellDef>ID</th>
311+
<td mat-cell *matCellDef="let row">{{row.userId}}</td>
312+
<td mat-footer-cell *matFooterCellDef>ID</td>
313+
</ng-container>
314+
315+
<tr mat-header-row *matHeaderRowDef="tableColumns; sticky: true"></tr>
316+
<tr mat-row *matRowDef="let row; columns: tableColumns;"></tr>
317+
<tr mat-footer-row *matFooterRowDef="tableColumns; sticky: true"></tr>
318+
</table>
319+
320+
306321
<h2>Selection list</h2>
307322
<mat-selection-list>
308323
<h3 mat-subheader>Groceries</h3>

0 commit comments

Comments
 (0)