Skip to content

fix(table): errors when rendering table with sticky elements on the server #12095

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 28, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/cdk/table/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ ng_module(
"//src/cdk/bidi",
"//src/cdk/collections",
"//src/cdk/coercion",
"//src/cdk/platform",
"@rxjs",
],
tsconfig = "//src/cdk:tsconfig-build.json",
Expand Down
11 changes: 9 additions & 2 deletions src/cdk/table/sticky-styler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,12 @@ export class StickyStyler {
* sticky positioning applied.
* @param direction The directionality context of the table (ltr/rtl); affects column positioning
* by reversing left/right positions.
* @param _isBrowser Whether the table is currently being rendered on the server or the client.
*/
constructor(private isNativeHtmlTable: boolean,
private stickCellCss: string,
public direction: Direction) { }
public direction: Direction,
private _isBrowser = true) { }

/**
* Clears the sticky positioning styles from the row and its cells by resetting the `position`
Expand Down Expand Up @@ -73,7 +75,7 @@ export class StickyStyler {
rows: HTMLElement[], stickyStartStates: boolean[], stickyEndStates: boolean[]) {
const hasStickyColumns =
stickyStartStates.some(state => state) || stickyEndStates.some(state => state);
if (!rows.length || !hasStickyColumns) {
if (!rows.length || !hasStickyColumns || !this._isBrowser) {
return;
}

Expand Down Expand Up @@ -111,6 +113,11 @@ export class StickyStyler {
*
*/
stickRows(rowsToStick: HTMLElement[], stickyStates: boolean[], position: 'top' | 'bottom') {
// Since we can't measure the rows on the server, we can't stick the rows properly.
if (!this._isBrowser) {
return;
}

// If positioning the rows to the bottom, reverse their order when evaluating the sticky
// position such that the last row stuck will be "bottom: 0px" and so on.
const rows = position === 'bottom' ? rowsToStick.reverse() : rowsToStick;
Expand Down
25 changes: 21 additions & 4 deletions src/cdk/table/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@ import {
ViewChild,
ViewContainerRef,
ViewEncapsulation,
Inject,
} from '@angular/core';
import {DOCUMENT} from '@angular/common';
import {BehaviorSubject, Observable, of as observableOf, Subject, Subscription} from 'rxjs';
import {takeUntil} from 'rxjs/operators';
import {CdkColumnDef} from './cell';
Expand All @@ -55,6 +57,7 @@ import {
import {coerceBooleanProperty} from '@angular/cdk/coercion';
import {StickyStyler} from './sticky-styler';
import {Direction, Directionality} from '@angular/cdk/bidi';
import {Platform} from '@angular/cdk/platform';

/** Interface used to provide an outlet for rows to be inserted into. */
export interface RowOutlet {
Expand Down Expand Up @@ -148,6 +151,8 @@ export interface RenderRow<T> {
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CdkTable<T> implements AfterContentChecked, CollectionViewer, OnDestroy, OnInit {
private _document: Document;

/** Latest data provided by the data source. */
protected _data: T[] | ReadonlyArray<T>;

Expand Down Expand Up @@ -359,11 +364,19 @@ export class CdkTable<T> implements AfterContentChecked, CollectionViewer, OnDes
protected readonly _changeDetectorRef: ChangeDetectorRef,
protected readonly _elementRef: ElementRef,
@Attribute('role') role: string,
@Optional() protected readonly _dir: Directionality) {
@Optional() protected readonly _dir: Directionality,
/**
* @deprecated
* @breaking-change 8.0.0 `_document` and `_platform` to
* be made into a required parameters.
*/
@Inject(DOCUMENT) _document?: any,
private _platform?: Platform) {
if (!role) {
this._elementRef.nativeElement.setAttribute('role', 'grid');
}

this._document = _document;
this._isNativeHtmlTable = this._elementRef.nativeElement.nodeName === 'TABLE';
}

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

for (const section of sections) {
const element = document.createElement(section.tag);
// @breaking-change 8.0.0 remove the `|| document` once the `_document` is a required param.
const documentRef = this._document || document;
const element = documentRef.createElement(section.tag);
element.appendChild(section.outlet.elementRef.nativeElement);
this._elementRef.nativeElement.appendChild(element);
}
Expand Down Expand Up @@ -999,7 +1014,9 @@ export class CdkTable<T> implements AfterContentChecked, CollectionViewer, OnDes
*/
private _setupStickyStyler() {
const direction: Direction = this._dir ? this._dir.value : 'ltr';
this._stickyStyler = new StickyStyler(this._isNativeHtmlTable, this.stickyCssClass, direction);
this._stickyStyler = new StickyStyler(this._isNativeHtmlTable,
// @breaking-change 8.0.0 remove the null check for `this._platform`.
this.stickyCssClass, direction, this._platform ? this._platform.isBrowser : true);
(this._dir ? this._dir.change : observableOf<Direction>())
.pipe(takeUntil(this._onDestroy))
.subscribe(value => {
Expand All @@ -1010,6 +1027,6 @@ export class CdkTable<T> implements AfterContentChecked, CollectionViewer, OnDes
}

/** Utility function that gets a merged list of the entries in a QueryList and values of a Set. */
function mergeQueryListAndSet<T>(queryList: QueryList<T>, set: Set<T>): T[] {
function mergeQueryListAndSet<T>(queryList: QueryList<T>, set: Set<T>): T[] {
return queryList.toArray().concat(Array.from(set));
}
1 change: 1 addition & 0 deletions src/lib/table/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ ng_module(
"//src/lib/paginator",
"//src/lib/sort",
"//src/cdk/table",
"//src/cdk/platform",
"@rxjs",
] + glob(["**/*.html"]),
tsconfig = "//src/lib:tsconfig-build.json",
Expand Down
15 changes: 15 additions & 0 deletions src/universal-app/kitchen-sink/kitchen-sink.html
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,21 @@ <h2>Material Table</h2>
<mat-row *cdkRowDef="let row; columns: tableColumns;"></mat-row>
</mat-table>

<h2>Native table with sticky header and footer</h2>

<table mat-table [dataSource]="tableDataSource">
<ng-container matColumnDef="userId">
<th mat-header-cell *matHeaderCellDef>ID</th>
<td mat-cell *matCellDef="let row">{{row.userId}}</td>
<td mat-footer-cell *matFooterCellDef>ID</td>
</ng-container>

<tr mat-header-row *matHeaderRowDef="tableColumns; sticky: true"></tr>
<tr mat-row *matRowDef="let row; columns: tableColumns;"></tr>
<tr mat-footer-row *matFooterRowDef="tableColumns; sticky: true"></tr>
</table>


<h2>Selection list</h2>
<mat-selection-list>
<h3 mat-subheader>Groceries</h3>
Expand Down