diff --git a/src/cdk/table/BUILD.bazel b/src/cdk/table/BUILD.bazel index bd6e1469962..cc610cb8173 100644 --- a/src/cdk/table/BUILD.bazel +++ b/src/cdk/table/BUILD.bazel @@ -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", diff --git a/src/cdk/table/sticky-styler.ts b/src/cdk/table/sticky-styler.ts index 10edad73e59..763cb0acc0e 100644 --- a/src/cdk/table/sticky-styler.ts +++ b/src/cdk/table/sticky-styler.ts @@ -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; diff --git a/src/cdk/table/table.ts b/src/cdk/table/table.ts index a98aab75a10..3c0594fe701 100644 --- a/src/cdk/table/table.ts +++ b/src/cdk/table/table.ts @@ -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 { changeDetection: ChangeDetectionStrategy.OnPush, }) export class CdkTable implements AfterContentChecked, CollectionViewer, OnDestroy, OnInit { + private _document: Document; + /** Latest data provided by the data source. */ protected _data: T[] | ReadonlyArray; @@ -359,11 +364,19 @@ export class CdkTable 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 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 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()) .pipe(takeUntil(this._onDestroy)) .subscribe(value => { @@ -1012,6 +1029,6 @@ export class CdkTable implements AfterContentChecked, CollectionViewer, OnDes } /** Utility function that gets a merged list of the entries in a QueryList and values of a Set. */ -function mergeQueryListAndSet(queryList: QueryList, set: Set): T[] { +function mergeQueryListAndSet(queryList: QueryList, set: Set): T[] { return queryList.toArray().concat(Array.from(set)); } diff --git a/src/lib/table/BUILD.bazel b/src/lib/table/BUILD.bazel index 109bc51e2fb..0764ea78d08 100644 --- a/src/lib/table/BUILD.bazel +++ b/src/lib/table/BUILD.bazel @@ -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", diff --git a/src/universal-app/kitchen-sink/kitchen-sink.html b/src/universal-app/kitchen-sink/kitchen-sink.html index f0477de92e5..0bd7e407a60 100644 --- a/src/universal-app/kitchen-sink/kitchen-sink.html +++ b/src/universal-app/kitchen-sink/kitchen-sink.html @@ -303,6 +303,21 @@ +

Native table with sticky header and footer

+ + + + + + + + + + + +
ID{{row.userId}}ID
+ +

Selection list

Groceries