Skip to content

Commit

Permalink
fix(table): errors when rendering table with sticky elements on the s…
Browse files Browse the repository at this point in the history
…erver

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.
  • Loading branch information
crisbeto committed Aug 25, 2018
1 parent 29d5173 commit 93a05d8
Show file tree
Hide file tree
Showing 5 changed files with 47 additions and 6 deletions.
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
* @deletion-target 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);
// @deletion-target 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,
// @deletion-target 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

0 comments on commit 93a05d8

Please sign in to comment.