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.
This commit is contained in:
Kristiyan Kostadinov
2018-08-28 17:02:49 +02:00
committed by Jeremy Elbourn
parent 6a1a70753e
commit 7e67fe936c
5 changed files with 47 additions and 6 deletions
+1
View File
@@ -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",
+9 -2
View File
@@ -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`
@@ -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;
}
@@ -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;
+21 -4
View File
@@ -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';
@@ -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 {
@@ -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>;
@@ -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';
}
@@ -949,7 +962,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);
}
@@ -1001,7 +1016,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 => {
@@ -1012,6 +1029,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
View File
@@ -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",
@@ -303,6 +303,21 @@
<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>