Skip to content

Commit 7e67fe9

Browse files
crisbetojelbourn
authored andcommitted
fix(table): errors when rendering table with sticky elements on the server (#12095)
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 6a1a707 commit 7e67fe9

File tree

5 files changed

+47
-6
lines changed

5 files changed

+47
-6
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`
@@ -73,7 +75,7 @@ export class StickyStyler {
7375
rows: HTMLElement[], stickyStartStates: boolean[], stickyEndStates: boolean[]) {
7476
const hasStickyColumns =
7577
stickyStartStates.some(state => state) || stickyEndStates.some(state => state);
76-
if (!rows.length || !hasStickyColumns) {
78+
if (!rows.length || !hasStickyColumns || !this._isBrowser) {
7779
return;
7880
}
7981

@@ -111,6 +113,11 @@ export class StickyStyler {
111113
*
112114
*/
113115
stickRows(rowsToStick: HTMLElement[], stickyStates: boolean[], position: 'top' | 'bottom') {
116+
// Since we can't measure the rows on the server, we can't stick the rows properly.
117+
if (!this._isBrowser) {
118+
return;
119+
}
120+
114121
// If positioning the rows to the bottom, reverse their order when evaluating the sticky
115122
// position such that the last row stuck will be "bottom: 0px" and so on.
116123
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[] | ReadonlyArray<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+
* @breaking-change 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

@@ -949,7 +962,9 @@ export class CdkTable<T> implements AfterContentChecked, CollectionViewer, OnDes
949962
];
950963

951964
for (const section of sections) {
952-
const element = document.createElement(section.tag);
965+
// @breaking-change 8.0.0 remove the `|| document` once the `_document` is a required param.
966+
const documentRef = this._document || document;
967+
const element = documentRef.createElement(section.tag);
953968
element.appendChild(section.outlet.elementRef.nativeElement);
954969
this._elementRef.nativeElement.appendChild(element);
955970
}
@@ -1001,7 +1016,9 @@ export class CdkTable<T> implements AfterContentChecked, CollectionViewer, OnDes
10011016
*/
10021017
private _setupStickyStyler() {
10031018
const direction: Direction = this._dir ? this._dir.value : 'ltr';
1004-
this._stickyStyler = new StickyStyler(this._isNativeHtmlTable, this.stickyCssClass, direction);
1019+
this._stickyStyler = new StickyStyler(this._isNativeHtmlTable,
1020+
// @breaking-change 8.0.0 remove the null check for `this._platform`.
1021+
this.stickyCssClass, direction, this._platform ? this._platform.isBrowser : true);
10051022
(this._dir ? this._dir.change : observableOf<Direction>())
10061023
.pipe(takeUntil(this._onDestroy))
10071024
.subscribe(value => {
@@ -1012,6 +1029,6 @@ export class CdkTable<T> implements AfterContentChecked, CollectionViewer, OnDes
10121029
}
10131030

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

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/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)