diff --git a/src/cdk-experimental/config.bzl b/src/cdk-experimental/config.bzl
index ab32c89f144c..2570c021489f 100644
--- a/src/cdk-experimental/config.bzl
+++ b/src/cdk-experimental/config.bzl
@@ -6,6 +6,7 @@ CDK_EXPERIMENTAL_ENTRYPOINTS = [
"menu",
"listbox",
"popover-edit",
+ "table",
"scrolling",
"selection",
"table-scroll-container",
diff --git a/src/cdk-experimental/table/BUILD.bazel b/src/cdk-experimental/table/BUILD.bazel
new file mode 100644
index 000000000000..1d6183c15362
--- /dev/null
+++ b/src/cdk-experimental/table/BUILD.bazel
@@ -0,0 +1,35 @@
+load("//tools:defaults.bzl", "ng_module", "ng_test_library", "ng_web_test_suite")
+
+package(default_visibility = ["//visibility:public"])
+
+ng_module(
+ name = "table",
+ srcs = glob(
+ ["**/*.ts"],
+ exclude = ["**/*.spec.ts"],
+ ),
+ module_name = "@angular/cdk-experimental/table",
+ deps = [
+ "//src/cdk/table",
+ "@npm//@angular/common",
+ "@npm//@angular/core",
+ "@npm//rxjs",
+ ],
+)
+
+ng_test_library(
+ name = "unit_test_sources",
+ srcs = glob(
+ ["**/*.spec.ts"],
+ exclude = ["**/*.e2e.spec.ts"],
+ ),
+ deps = [
+ ":table",
+ "//src/cdk/table",
+ ],
+)
+
+ng_web_test_suite(
+ name = "unit_tests",
+ deps = [":unit_test_sources"],
+)
diff --git a/src/cdk-experimental/table/index.ts b/src/cdk-experimental/table/index.ts
new file mode 100644
index 000000000000..676ca90f1ffa
--- /dev/null
+++ b/src/cdk-experimental/table/index.ts
@@ -0,0 +1,9 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+export * from './public-api';
diff --git a/src/cdk-experimental/table/public-api.ts b/src/cdk-experimental/table/public-api.ts
new file mode 100644
index 000000000000..0f0b4879b5b4
--- /dev/null
+++ b/src/cdk-experimental/table/public-api.ts
@@ -0,0 +1,10 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+export * from './scrollable-table-body-layout';
+export * from './scrollable-table-body-module';
diff --git a/src/cdk-experimental/table/scrollable-table-body-layout.spec.ts b/src/cdk-experimental/table/scrollable-table-body-layout.spec.ts
new file mode 100644
index 000000000000..a6cd17506b84
--- /dev/null
+++ b/src/cdk-experimental/table/scrollable-table-body-layout.spec.ts
@@ -0,0 +1,89 @@
+import {Component, Input, ViewChild} from '@angular/core';
+import {async, fakeAsync, TestBed} from '@angular/core/testing';
+import {CdkTable, CdkTableModule} from '@angular/cdk/table';
+import {CdkScrollableTableBodyModule} from './scrollable-table-body-module';
+
+
+describe('CdkScrollableTableBody', () => {
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [CdkScrollableTableBodyModule, CdkTableModule],
+ declarations: [CdkTableWithScrollableBody],
+ }).compileComponents();
+ }));
+
+ it('wraps row outlets in container', fakeAsync(() => {
+ const fixture = TestBed.createComponent(CdkTableWithScrollableBody);
+ const testComponent = fixture.componentInstance;
+ fixture.detectChanges();
+ const table = testComponent.table;
+ const headerOutletContainer = table._headerRowOutlet.elementRef.nativeElement.parentElement;
+ const rowOutletContainer = table._rowOutlet.elementRef.nativeElement.parentElement;
+ const footerOutletContainer = table._footerRowOutlet.elementRef.nativeElement.parentElement;
+ testComponent.maxHeight = '100px';
+
+ expect(headerOutletContainer.classList.contains('cdk-table-scrollable-table-header'))
+ .toBe(true);
+ expect(rowOutletContainer.classList.contains('cdk-table-scrollable-table-body'))
+ .toBe(true);
+ expect(footerOutletContainer.classList.contains('cdk-table-scrollable-table-footer'))
+ .toBe(true);
+ }));
+
+ it('updates DOM when max height is changed', fakeAsync(() => {
+ const fixture = TestBed.createComponent(CdkTableWithScrollableBody);
+ const testComponent = fixture.componentInstance;
+ fixture.detectChanges();
+ const table = testComponent.table;
+ const rowOutletContainer = table._rowOutlet.elementRef.nativeElement.parentElement;
+
+ testComponent.maxHeight = '100px';
+ fixture.detectChanges();
+ expect(rowOutletContainer.style.maxHeight).toBe('100px');
+
+ testComponent.maxHeight = '200px';
+ fixture.detectChanges();
+ expect(rowOutletContainer.style.maxHeight).toBe('200px');
+ }));
+});
+
+interface TestData {
+ a: string;
+ b: string;
+ c: string;
+}
+
+@Component({
+ template: `
+
+
+ Column A
+ {{row.a}}
+ Footer A
+
+
+
+ Column B
+ {{row.b}}
+ Footer B
+
+
+
+ Column C
+ {{row.c}}
+ Footer C
+
+
+
+
+
+
+ `
+})
+class CdkTableWithScrollableBody {
+ dataSource: [];
+ columnsToRender = ['column_a', 'column_b', 'column_c'];
+
+ @Input() maxHeight!: string;
+ @ViewChild(CdkTable) table: CdkTable;
+}
diff --git a/src/cdk-experimental/table/scrollable-table-body-layout.ts b/src/cdk-experimental/table/scrollable-table-body-layout.ts
new file mode 100644
index 000000000000..38e8892a4a72
--- /dev/null
+++ b/src/cdk-experimental/table/scrollable-table-body-layout.ts
@@ -0,0 +1,115 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+import {Directive, Inject, Injectable, Input} from '@angular/core';
+import {CdkTable} from '@angular/cdk/table/table';
+import {DOCUMENT} from '@angular/common';
+import {
+ _TABLE_LAYOUT_STRATEGY,
+ _TableLayoutStrategy,
+ _StandardTableLayoutStrategy,
+} from '@angular/cdk/table/table-layout-strategy';
+
+/**
+ * A {@link _TableLayoutStrategy} that enables scrollable body content for flex tables.
+ */
+@Injectable()
+export class ScrollableTableBodyLayoutStrategy implements _TableLayoutStrategy {
+ private readonly _document: Document;
+ private defaultLayout: _StandardTableLayoutStrategy;
+ private _pendingMaxHeight = 'none';
+ private _scrollViewport?: HTMLElement;
+ readonly headerCssClass = 'cdk-table-scrollable-table-header';
+ readonly bodyCssClass = 'cdk-table-scrollable-table-body';
+ readonly footerCssClass = 'cdk-table-scrollable-table-footer';
+
+ constructor(@Inject(DOCUMENT) document: any) {
+ this._document = document;
+ this.defaultLayout = new _StandardTableLayoutStrategy(this._document);
+ }
+
+ /**
+ * Returns the DOM structure for a native table. Scrollable body content is not supported for
+ * native tables. Return `null` to use the default {@link CdkTable} native table layout.
+ */
+ getNativeLayout(table: CdkTable): DocumentFragment {
+ return this.defaultLayout.getNativeLayout(table);
+ }
+
+ /**
+ * Returns the DOM structure for a flex table with scrollable body content. Each row outlet
+ * (header, body, footer) is wrapped in a separate container. The specified max height is applied
+ * to the body row outlet to make its content scrollable.
+ */
+ getFlexLayout(table: CdkTable): DocumentFragment {
+ const documentFragment = this._document.createDocumentFragment();
+ const sections = [
+ {cssClass: this.headerCssClass, outlets: [table._headerRowOutlet]},
+ {cssClass: this.bodyCssClass, outlets: [table._rowOutlet, table._noDataRowOutlet]},
+ {cssClass: this.footerCssClass, outlets: [table._footerRowOutlet]},
+ ];
+
+ for (const section of sections) {
+ const element = this._document.createElement('div');
+ element.classList.add(section.cssClass);
+ for (const outlet of section.outlets) {
+ element.appendChild(outlet.elementRef.nativeElement);
+ }
+
+ documentFragment.appendChild(element);
+ }
+
+ this._scrollViewport = documentFragment.querySelector(`.${this.bodyCssClass}`) as HTMLElement;
+ this._scrollViewport!.style.overflow = 'auto';
+ this._applyMaxHeight(this._scrollViewport!, this._pendingMaxHeight);
+
+ return documentFragment;
+ }
+
+ /**
+ * Show a scroll bar if the table's body exceeds this height. The height may be specified with
+ * any valid CSS unit of measurement.
+ */
+ setMaxHeight(v: string) {
+ this._pendingMaxHeight = v;
+ if (this._scrollViewport) {
+ this._applyMaxHeight(this._scrollViewport, v);
+ }
+ }
+
+ private _applyMaxHeight(el: HTMLElement, maxHeight: string) {
+ el.style.maxHeight = maxHeight;
+ }
+}
+
+/** A directive that enables scrollable body content for flex tables. */
+@Directive({
+ selector: 'cdk-table[scrollableBody]',
+ providers: [
+ {provide: _TABLE_LAYOUT_STRATEGY, useClass: ScrollableTableBodyLayoutStrategy},
+ ]
+})
+export class CdkScrollableTableBody {
+ /**
+ * Show a scroll bar if the table's body exceeds this height. The height may be specified with
+ * any valid CSS unit of measurement.
+ */
+ @Input('scrollableBody')
+ get maxHeight() {
+ return this._maxHeight;
+ }
+ set maxHeight(v: string) {
+ this._maxHeight = v;
+ this._layoutStrategy.setMaxHeight(v);
+ }
+ private _maxHeight = '';
+
+ constructor(@Inject(_TABLE_LAYOUT_STRATEGY)
+ private readonly _layoutStrategy: ScrollableTableBodyLayoutStrategy) {
+ }
+}
diff --git a/src/cdk-experimental/table/scrollable-table-body-module.ts b/src/cdk-experimental/table/scrollable-table-body-module.ts
new file mode 100644
index 000000000000..0c687f39c547
--- /dev/null
+++ b/src/cdk-experimental/table/scrollable-table-body-module.ts
@@ -0,0 +1,22 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+import {NgModule} from '@angular/core';
+import {CdkScrollableTableBody} from './scrollable-table-body-layout';
+
+export {CdkScrollableTableBody};
+
+const EXPORTED_DECLARATIONS = [
+ CdkScrollableTableBody,
+];
+
+@NgModule({
+ exports: EXPORTED_DECLARATIONS,
+ declarations: EXPORTED_DECLARATIONS,
+})
+export class CdkScrollableTableBodyModule { }
diff --git a/src/cdk/table/table-layout-strategy.ts b/src/cdk/table/table-layout-strategy.ts
new file mode 100644
index 000000000000..e37d90d386ac
--- /dev/null
+++ b/src/cdk/table/table-layout-strategy.ts
@@ -0,0 +1,58 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+import {Inject, InjectionToken} from '@angular/core';
+import {CdkTable} from '@angular/cdk/table/table';
+import {DOCUMENT} from '@angular/common';
+
+/** Interface for a service that constructs the DOM structure for a {@link CdkTable}. */
+export interface _TableLayoutStrategy {
+ /** Constructs the DOM structure for a native table. */
+ getNativeLayout(table: CdkTable): DocumentFragment;
+ /** Constructs the DOM structure for a flex table. */
+ getFlexLayout(table: CdkTable): DocumentFragment;
+}
+
+/** Injection token for {@link _TableLayoutStrategy}. */
+export const _TABLE_LAYOUT_STRATEGY =
+ new InjectionToken<_TableLayoutStrategy>('_TableLayoutStrategy');
+
+
+export class _StandardTableLayoutStrategy implements _TableLayoutStrategy {
+ private readonly _document: Document;
+
+ constructor(@Inject(DOCUMENT) document: any) {
+ this._document = document;
+ }
+
+ getNativeLayout(table: CdkTable): DocumentFragment {
+ const documentFragment = this._document.createDocumentFragment();
+ const sections = [
+ {tag: 'thead', outlets: [table._headerRowOutlet]},
+ {tag: 'tbody', outlets: [table._rowOutlet, table._noDataRowOutlet]},
+ {tag: 'tfoot', outlets: [table._footerRowOutlet]},
+ ];
+
+ for (const section of sections) {
+ const element = this._document.createElement(section.tag);
+ element.setAttribute('role', 'rowgroup');
+
+ for (const outlet of section.outlets) {
+ element.appendChild(outlet.elementRef.nativeElement);
+ }
+
+ documentFragment.appendChild(element);
+ }
+
+ return documentFragment;
+ }
+
+ getFlexLayout(table: CdkTable): DocumentFragment {
+ return this._document.createDocumentFragment();
+ }
+}
diff --git a/src/cdk/table/table.ts b/src/cdk/table/table.ts
index 634bb190bd12..1ae1ded3930e 100644
--- a/src/cdk/table/table.ts
+++ b/src/cdk/table/table.ts
@@ -81,6 +81,11 @@ import {
} from './table-errors';
import {STICKY_POSITIONING_LISTENER, StickyPositioningListener} from './sticky-position-listener';
import {CDK_TABLE} from './tokens';
+import {
+ _StandardTableLayoutStrategy,
+ _TABLE_LAYOUT_STRATEGY,
+ _TableLayoutStrategy
+} from './table-layout-strategy';
/** Interface used to provide an outlet for rows to be inserted into. */
export interface RowOutlet {
@@ -486,22 +491,23 @@ export class CdkTable implements AfterContentChecked, CollectionViewer, OnDes
protected readonly _elementRef: ElementRef, @Attribute('role') role: string,
@Optional() protected readonly _dir: Directionality, @Inject(DOCUMENT) _document: any,
private _platform: Platform,
-
/**
* @deprecated `_coalescedStyleScheduler`, `_viewRepeater` and `_viewportRuler`
* parameters to become required.
* @breaking-change 11.0.0
*/
@Optional() @Inject(_VIEW_REPEATER_STRATEGY)
- protected readonly _viewRepeater?: _ViewRepeater, RowContext>,
+ protected readonly _viewRepeater?: _ViewRepeater, RowContext>,
@Optional() @Inject(_COALESCED_STYLE_SCHEDULER)
- protected readonly _coalescedStyleScheduler?: _CoalescedStyleScheduler,
+ protected readonly _coalescedStyleScheduler?: _CoalescedStyleScheduler,
@Optional() @SkipSelf() @Inject(STICKY_POSITIONING_LISTENER)
protected readonly _stickyPositioningListener?: StickyPositioningListener,
// Optional for backwards compatibility. The viewport ruler is provided in root. Therefore,
// this property will never be null.
// tslint:disable-next-line: lightweight-tokens
- @Optional() private readonly _viewportRuler?: ViewportRuler) {
+ @Optional() private readonly _viewportRuler?: ViewportRuler,
+ @Optional() @Inject(_TABLE_LAYOUT_STRATEGY)
+ @Optional() private readonly _layoutStrategy?: _TableLayoutStrategy|null) {
if (!role) {
this._elementRef.nativeElement.setAttribute('role', 'grid');
}
@@ -512,10 +518,7 @@ export class CdkTable implements AfterContentChecked, CollectionViewer, OnDes
ngOnInit() {
this._setupStickyStyler();
-
- if (this._isNativeHtmlTable) {
- this._applyNativeTableSections();
- }
+ this._initTableLayout();
// Set up the trackBy function so that it uses the `RenderRow` as its identity by default. If
// the user has provided a custom trackBy, return the result of that function as evaluated
@@ -1153,28 +1156,14 @@ export class CdkTable implements AfterContentChecked, CollectionViewer, OnDes
});
}
- /** Adds native table sections (e.g. tbody) and moves the row outlets into them. */
- private _applyNativeTableSections() {
- const documentFragment = this._document.createDocumentFragment();
- const sections = [
- {tag: 'thead', outlets: [this._headerRowOutlet]},
- {tag: 'tbody', outlets: [this._rowOutlet, this._noDataRowOutlet]},
- {tag: 'tfoot', outlets: [this._footerRowOutlet]},
- ];
-
- for (const section of sections) {
- const element = this._document.createElement(section.tag);
- element.setAttribute('role', 'rowgroup');
-
- for (const outlet of section.outlets) {
- element.appendChild(outlet.elementRef.nativeElement);
- }
-
- documentFragment.appendChild(element);
- }
+ private _initTableLayout() {
+ const layoutStrategy = this._layoutStrategy || new _StandardTableLayoutStrategy(this._document);
+ const layout = this._isNativeHtmlTable
+ ? layoutStrategy.getNativeLayout(this)
+ : layoutStrategy.getFlexLayout(this);
// Use a DocumentFragment so we don't hit the DOM on each iteration.
- this._elementRef.nativeElement.appendChild(documentFragment);
+ this._elementRef.nativeElement.appendChild(layout);
}
/**
diff --git a/src/components-examples/BUILD.bazel b/src/components-examples/BUILD.bazel
index 9ad3bb8ad421..ff8380765d1c 100644
--- a/src/components-examples/BUILD.bazel
+++ b/src/components-examples/BUILD.bazel
@@ -47,6 +47,7 @@ ALL_EXAMPLES = [
"//src/components-examples/material-experimental/popover-edit",
"//src/components-examples/material-experimental/mdc-card",
"//src/components-examples/material-experimental/mdc-form-field",
+ "//src/components-examples/material-experimental/scrollable-table-body",
"//src/components-examples/material-experimental/selection",
"//src/components-examples/cdk/tree",
"//src/components-examples/cdk/text-field",
@@ -61,6 +62,7 @@ ALL_EXAMPLES = [
"//src/components-examples/cdk/overlay",
"//src/components-examples/cdk-experimental/menu",
"//src/components-examples/cdk-experimental/popover-edit",
+ "//src/components-examples/cdk-experimental/scrollable-table-body",
"//src/components-examples/cdk-experimental/selection",
]
diff --git a/src/components-examples/cdk-experimental/scrollable-table-body/BUILD.bazel b/src/components-examples/cdk-experimental/scrollable-table-body/BUILD.bazel
new file mode 100644
index 000000000000..18422a173169
--- /dev/null
+++ b/src/components-examples/cdk-experimental/scrollable-table-body/BUILD.bazel
@@ -0,0 +1,27 @@
+load("//tools:defaults.bzl", "ng_module")
+
+package(default_visibility = ["//visibility:public"])
+
+ng_module(
+ name = "scrollable-table-body",
+ srcs = glob(["**/*.ts"]),
+ assets = glob([
+ "**/*.html",
+ "**/*.css",
+ ]),
+ module_name = "@angular/components-examples/cdk-experimental/scrollable-table-body",
+ deps = [
+ "//src/cdk-experimental/table",
+ "//src/cdk/table",
+ "//src/material/button",
+ ],
+)
+
+filegroup(
+ name = "source-files",
+ srcs = glob([
+ "**/*.html",
+ "**/*.css",
+ "**/*.ts",
+ ]),
+)
diff --git a/src/components-examples/cdk-experimental/scrollable-table-body/cdk-scrollable-table-body-flex/cdk-scrollable-table-body-flex-example.css b/src/components-examples/cdk-experimental/scrollable-table-body/cdk-scrollable-table-body-flex/cdk-scrollable-table-body-flex-example.css
new file mode 100644
index 000000000000..f0d18e4f1d11
--- /dev/null
+++ b/src/components-examples/cdk-experimental/scrollable-table-body/cdk-scrollable-table-body-flex/cdk-scrollable-table-body-flex-example.css
@@ -0,0 +1,10 @@
+/**
+ * Add basic flex styling so that the cells evenly space themselves in the row.
+ */
+cdk-row, cdk-header-row, cdk-footer-row {
+ display: flex;
+}
+
+cdk-cell, cdk-header-cell, cdk-footer-cell {
+ flex: 1;
+}
diff --git a/src/components-examples/cdk-experimental/scrollable-table-body/cdk-scrollable-table-body-flex/cdk-scrollable-table-body-flex-example.html b/src/components-examples/cdk-experimental/scrollable-table-body/cdk-scrollable-table-body-flex/cdk-scrollable-table-body-flex-example.html
new file mode 100644
index 000000000000..5f166045434e
--- /dev/null
+++ b/src/components-examples/cdk-experimental/scrollable-table-body/cdk-scrollable-table-body-flex/cdk-scrollable-table-body-flex-example.html
@@ -0,0 +1,33 @@
+
+
+
+
+
Max Height: {{maxHeight}}
+
+
+
+ No.
+ {{element.position}}
+
+
+
+
+ Name
+ {{element.name}}
+
+
+
+
+ Weight
+ {{element.weight}}
+
+
+
+
+ Symbol
+ {{element.symbol}}
+
+
+
+
+
diff --git a/src/components-examples/cdk-experimental/scrollable-table-body/cdk-scrollable-table-body-flex/cdk-scrollable-table-body-flex-example.ts b/src/components-examples/cdk-experimental/scrollable-table-body/cdk-scrollable-table-body-flex/cdk-scrollable-table-body-flex-example.ts
new file mode 100644
index 000000000000..4d225684be1d
--- /dev/null
+++ b/src/components-examples/cdk-experimental/scrollable-table-body/cdk-scrollable-table-body-flex/cdk-scrollable-table-body-flex-example.ts
@@ -0,0 +1,88 @@
+import {DataSource} from '@angular/cdk/collections';
+import {Component} from '@angular/core';
+import {BehaviorSubject, Observable} from 'rxjs';
+
+export interface PeriodicElement {
+ name: string;
+ position: number;
+ symbol: string;
+ weight: number;
+}
+
+const ELEMENT_DATA: PeriodicElement[] = [
+ {position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H'},
+ {position: 2, name: 'Helium', weight: 4.0026, symbol: 'He'},
+ {position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li'},
+ {position: 4, name: 'Beryllium', weight: 9.0122, symbol: 'Be'},
+ {position: 5, name: 'Boron', weight: 10.811, symbol: 'B'},
+ {position: 6, name: 'Carbon', weight: 12.0107, symbol: 'C'},
+ {position: 7, name: 'Nitrogen', weight: 14.0067, symbol: 'N'},
+ {position: 8, name: 'Oxygen', weight: 15.9994, symbol: 'O'},
+ {position: 9, name: 'Fluorine', weight: 18.9984, symbol: 'F'},
+ {position: 10, name: 'Neon', weight: 20.1797, symbol: 'Ne'},
+];
+
+/**
+ * @title Example of {@link CdkScrollableTableBody} for the CDK flex table.
+ */
+@Component({
+ selector: 'cdk-scrollable-table-body-flex-example',
+ styleUrls: ['cdk-scrollable-table-body-flex-example.css'],
+ templateUrl: 'cdk-scrollable-table-body-flex-example.html',
+})
+export class CdkScrollableTableBodyFlexExample {
+ private readonly _allowedMaxHeights = ['100px', '200px', '400px', '800px'];
+ private _maxHeightIndex = 1;
+ displayedColumns: string[] = ['position', 'name', 'weight', 'symbol'];
+ dataSource = new ExampleDataSource();
+
+ get maxHeight() {
+ return this._allowedMaxHeights[this._maxHeightIndex];
+ }
+
+ addRow() {
+ this.dataSource.addRow();
+ }
+
+ removeRow() {
+ this.dataSource.removeRow();
+ }
+
+ nextMaxHeight() {
+ this._maxHeightIndex = (this._maxHeightIndex + 1) % this._allowedMaxHeights.length;
+ }
+}
+
+/**
+ * Data source to provide what data should be rendered in the table. Note that the data source
+ * can retrieve its data in any way. In this case, the data source is provided a reference
+ * to a common data base, ExampleDatabase. It is not the data source's responsibility to manage
+ * the underlying data. Instead, it only needs to take the data and send the table exactly what
+ * should be rendered.
+ */
+export class ExampleDataSource extends DataSource {
+ private _data = [...ELEMENT_DATA];
+ /** Stream of data that is provided to the table. */
+ data = new BehaviorSubject(this._data);
+
+ addRow() {
+ const row = {...ELEMENT_DATA[this._data.length % ELEMENT_DATA.length]};
+ row.position = this._data.length + 1;
+ this._data.push(row);
+ this.data.next(this._data);
+ }
+
+ removeRow() {
+ this._data.pop();
+ this.data.next(this._data);
+ }
+
+ /** Connect function called by the table to retrieve one stream containing the data to render. */
+ connect(): Observable {
+ return this.data;
+ }
+
+ disconnect() {
+ this.data.complete();
+ }
+}
diff --git a/src/components-examples/cdk-experimental/scrollable-table-body/index.ts b/src/components-examples/cdk-experimental/scrollable-table-body/index.ts
new file mode 100644
index 000000000000..2fdb7cb4d131
--- /dev/null
+++ b/src/components-examples/cdk-experimental/scrollable-table-body/index.ts
@@ -0,0 +1,30 @@
+import {NgModule} from '@angular/core';
+import {CdkTableModule} from '@angular/cdk/table';
+import {
+ CdkScrollableTableBodyModule,
+} from '@angular/cdk-experimental/table/scrollable-table-body-module';
+import {
+ CdkScrollableTableBodyFlexExample,
+} from './cdk-scrollable-table-body-flex/cdk-scrollable-table-body-flex-example';
+import {MatButtonModule} from '@angular/material/button';
+
+export {
+ CdkScrollableTableBodyFlexExample,
+};
+
+const EXAMPLES = [
+ CdkScrollableTableBodyFlexExample,
+];
+
+@NgModule({
+ imports: [
+ CdkScrollableTableBodyModule,
+ CdkTableModule,
+ MatButtonModule,
+ ],
+ declarations: EXAMPLES,
+ exports: EXAMPLES,
+ entryComponents: EXAMPLES,
+})
+export class CdkScrollableTableBodyExamplesModule {
+}
diff --git a/src/components-examples/material-experimental/scrollable-table-body/BUILD.bazel b/src/components-examples/material-experimental/scrollable-table-body/BUILD.bazel
new file mode 100644
index 000000000000..3ffa897f3254
--- /dev/null
+++ b/src/components-examples/material-experimental/scrollable-table-body/BUILD.bazel
@@ -0,0 +1,28 @@
+load("//tools:defaults.bzl", "ng_module")
+
+package(default_visibility = ["//visibility:public"])
+
+ng_module(
+ name = "scrollable-table-body",
+ srcs = glob(["**/*.ts"]),
+ assets = glob([
+ "**/*.html",
+ "**/*.css",
+ ]),
+ module_name = "@angular/components-examples/material-experimental/scrollable-table-body",
+ deps = [
+ "//src/cdk-experimental/table",
+ "//src/material-experimental/table",
+ "//src/material/button",
+ "//src/material/table",
+ ],
+)
+
+filegroup(
+ name = "source-files",
+ srcs = glob([
+ "**/*.html",
+ "**/*.css",
+ "**/*.ts",
+ ]),
+)
diff --git a/src/components-examples/material-experimental/scrollable-table-body/index.ts b/src/components-examples/material-experimental/scrollable-table-body/index.ts
new file mode 100644
index 000000000000..d02746454046
--- /dev/null
+++ b/src/components-examples/material-experimental/scrollable-table-body/index.ts
@@ -0,0 +1,32 @@
+import {NgModule} from '@angular/core';
+import {
+ CdkScrollableTableBodyModule,
+} from '@angular/cdk-experimental/table/scrollable-table-body-module';
+import {
+ MatScrollableTableBodyFlexExample,
+} from './mat-scrollable-table-body-flex/mat-scrollable-table-body-flex-example';
+import {MatTableModule} from '@angular/material/table';
+import {MatButtonModule} from '@angular/material/button';
+import {MatScrollableTableBodyModule} from '@angular/material-experimental/table';
+
+export {
+ MatScrollableTableBodyFlexExample,
+};
+
+const EXAMPLES = [
+ MatScrollableTableBodyFlexExample,
+];
+
+@NgModule({
+ imports: [
+ CdkScrollableTableBodyModule,
+ MatScrollableTableBodyModule,
+ MatButtonModule,
+ MatTableModule,
+ ],
+ declarations: EXAMPLES,
+ exports: EXAMPLES,
+ entryComponents: EXAMPLES,
+})
+export class MatScrollableTableBodyExamplesModule {
+}
diff --git a/src/components-examples/material-experimental/scrollable-table-body/mat-scrollable-table-body-flex/mat-scrollable-table-body-flex-example.html b/src/components-examples/material-experimental/scrollable-table-body/mat-scrollable-table-body-flex/mat-scrollable-table-body-flex-example.html
new file mode 100644
index 000000000000..9aa81028b3ea
--- /dev/null
+++ b/src/components-examples/material-experimental/scrollable-table-body/mat-scrollable-table-body-flex/mat-scrollable-table-body-flex-example.html
@@ -0,0 +1,33 @@
+
+
+
+
+
Max Height: {{maxHeight}}
+
+
+
+ No.
+ {{element.position}}
+
+
+
+
+ Name
+ {{element.name}}
+
+
+
+
+ Weight
+ {{element.weight}}
+
+
+
+
+ Symbol
+ {{element.symbol}}
+
+
+
+
+
diff --git a/src/components-examples/material-experimental/scrollable-table-body/mat-scrollable-table-body-flex/mat-scrollable-table-body-flex-example.ts b/src/components-examples/material-experimental/scrollable-table-body/mat-scrollable-table-body-flex/mat-scrollable-table-body-flex-example.ts
new file mode 100644
index 000000000000..7c4301c91d6a
--- /dev/null
+++ b/src/components-examples/material-experimental/scrollable-table-body/mat-scrollable-table-body-flex/mat-scrollable-table-body-flex-example.ts
@@ -0,0 +1,87 @@
+import {DataSource} from '@angular/cdk/collections';
+import {Component} from '@angular/core';
+import {BehaviorSubject, Observable} from 'rxjs';
+
+export interface PeriodicElement {
+ name: string;
+ position: number;
+ symbol: string;
+ weight: number;
+}
+
+const ELEMENT_DATA: PeriodicElement[] = [
+ {position: 1, name: 'Hydrogen', weight: 1.0079, symbol: 'H'},
+ {position: 2, name: 'Helium', weight: 4.0026, symbol: 'He'},
+ {position: 3, name: 'Lithium', weight: 6.941, symbol: 'Li'},
+ {position: 4, name: 'Beryllium', weight: 9.0122, symbol: 'Be'},
+ {position: 5, name: 'Boron', weight: 10.811, symbol: 'B'},
+ {position: 6, name: 'Carbon', weight: 12.0107, symbol: 'C'},
+ {position: 7, name: 'Nitrogen', weight: 14.0067, symbol: 'N'},
+ {position: 8, name: 'Oxygen', weight: 15.9994, symbol: 'O'},
+ {position: 9, name: 'Fluorine', weight: 18.9984, symbol: 'F'},
+ {position: 10, name: 'Neon', weight: 20.1797, symbol: 'Ne'},
+];
+
+/**
+ * @title Example of {@link CdkScrollableTableBody} for the material flex table.
+ */
+@Component({
+ selector: 'mat-scrollable-table-body-flex-example',
+ templateUrl: 'mat-scrollable-table-body-flex-example.html',
+})
+export class MatScrollableTableBodyFlexExample {
+ private readonly _allowedMaxHeights = ['100px', '200px', '400px', '800px'];
+ private _maxHeightIndex = 1;
+ displayedColumns: string[] = ['position', 'name', 'weight', 'symbol'];
+ dataSource = new ExampleDataSource();
+
+ get maxHeight() {
+ return this._allowedMaxHeights[this._maxHeightIndex];
+ }
+
+ addRow() {
+ this.dataSource.addRow();
+ }
+
+ removeRow() {
+ this.dataSource.removeRow();
+ }
+
+ nextMaxHeight() {
+ this._maxHeightIndex = (this._maxHeightIndex + 1) % this._allowedMaxHeights.length;
+ }
+}
+
+/**
+ * Data source to provide what data should be rendered in the table. Note that the data source
+ * can retrieve its data in any way. In this case, the data source is provided a reference
+ * to a common data base, ExampleDatabase. It is not the data source's responsibility to manage
+ * the underlying data. Instead, it only needs to take the data and send the table exactly what
+ * should be rendered.
+ */
+export class ExampleDataSource extends DataSource {
+ private _data = [...ELEMENT_DATA];
+ /** Stream of data that is provided to the table. */
+ data = new BehaviorSubject(this._data);
+
+ addRow() {
+ const row = {...ELEMENT_DATA[this._data.length % ELEMENT_DATA.length]};
+ row.position = this._data.length + 1;
+ this._data.push(row);
+ this.data.next(this._data);
+ }
+
+ removeRow() {
+ this._data.pop();
+ this.data.next(this._data);
+ }
+
+ /** Connect function called by the table to retrieve one stream containing the data to render. */
+ connect(): Observable {
+ return this.data;
+ }
+
+ disconnect() {
+ this.data.complete();
+ }
+}
diff --git a/src/dev-app/BUILD.bazel b/src/dev-app/BUILD.bazel
index 0ba4c94d89ab..dcc6cf3e5b01 100644
--- a/src/dev-app/BUILD.bazel
+++ b/src/dev-app/BUILD.bazel
@@ -78,6 +78,7 @@ ng_module(
"//src/dev-app/radio",
"//src/dev-app/ripple",
"//src/dev-app/screen-type",
+ "//src/dev-app/scrollable-table-body",
"//src/dev-app/select",
"//src/dev-app/selection",
"//src/dev-app/sidenav",
diff --git a/src/dev-app/dev-app/dev-app-layout.ts b/src/dev-app/dev-app/dev-app-layout.ts
index 985c4e00f19b..97a68589e17e 100644
--- a/src/dev-app/dev-app/dev-app-layout.ts
+++ b/src/dev-app/dev-app/dev-app-layout.ts
@@ -66,6 +66,7 @@ export class DevAppLayout {
{name: 'Radio', route: '/radio'},
{name: 'Ripple', route: '/ripple'},
{name: 'Screen Type', route: '/screen-type'},
+ {name: 'Scrollable table body', route: '/scrollable-table-body'},
{name: 'Select', route: '/select'},
{name: 'Selection', route: '/selection'},
{name: 'Sidenav', route: '/sidenav'},
diff --git a/src/dev-app/dev-app/routes.ts b/src/dev-app/dev-app/routes.ts
index 1f001264fe43..6b5e5adbf67f 100644
--- a/src/dev-app/dev-app/routes.ts
+++ b/src/dev-app/dev-app/routes.ts
@@ -130,6 +130,11 @@ export const DEV_APP_ROUTES: Routes = [
},
{path: 'radio', loadChildren: 'radio/radio-demo-module#RadioDemoModule'},
{path: 'ripple', loadChildren: 'ripple/ripple-demo-module#RippleDemoModule'},
+ {
+ path: 'scrollable-table-body',
+ loadChildren:
+ 'scrollable-table-body/scrollable-table-body-demo-module#ScrollableTableBodyDemoModule'
+ },
{path: 'select', loadChildren: 'select/select-demo-module#SelectDemoModule'},
{path: 'sidenav', loadChildren: 'sidenav/sidenav-demo-module#SidenavDemoModule'},
{
diff --git a/src/dev-app/scrollable-table-body/BUILD b/src/dev-app/scrollable-table-body/BUILD
new file mode 100644
index 000000000000..9aef1e71faac
--- /dev/null
+++ b/src/dev-app/scrollable-table-body/BUILD
@@ -0,0 +1,14 @@
+load("//tools:defaults.bzl", "ng_module")
+
+package(default_visibility = ["//visibility:public"])
+
+ng_module(
+ name = "scrollable-table-body",
+ srcs = glob(["**/*.ts"]),
+ deps = [
+ "//src/components-examples/material-experimental/scrollable-table-body",
+ "//src/dev-app/example",
+ "@npm//@angular/forms",
+ "@npm//@angular/router",
+ ],
+)
diff --git a/src/dev-app/scrollable-table-body/scrollable-table-body-demo-module.ts b/src/dev-app/scrollable-table-body/scrollable-table-body-demo-module.ts
new file mode 100644
index 000000000000..60f7c25b2216
--- /dev/null
+++ b/src/dev-app/scrollable-table-body/scrollable-table-body-demo-module.ts
@@ -0,0 +1,28 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+import {NgModule} from '@angular/core';
+import {RouterModule} from '@angular/router';
+import {ScrollableTableBodyDemo} from './scrollable-table-body-demo';
+/*import {
+ CdkScrollableTableBodyExamplesModule,
+} from '@angular/components-examples/cdk-experimental/scrollable-table-body';*/
+import {
+ MatScrollableTableBodyExamplesModule,
+} from '@angular/components-examples/material-experimental/scrollable-table-body';
+
+@NgModule({
+ imports: [
+ // CdkScrollableTableBodyExamplesModule,
+ MatScrollableTableBodyExamplesModule,
+ RouterModule.forChild([{path: '', component: ScrollableTableBodyDemo}]),
+ ],
+ declarations: [ScrollableTableBodyDemo],
+})
+export class ScrollableTableBodyDemoModule {
+}
diff --git a/src/dev-app/scrollable-table-body/scrollable-table-body-demo.ts b/src/dev-app/scrollable-table-body/scrollable-table-body-demo.ts
new file mode 100644
index 000000000000..5f6852d0852f
--- /dev/null
+++ b/src/dev-app/scrollable-table-body/scrollable-table-body-demo.ts
@@ -0,0 +1,20 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+import {Component} from '@angular/core';
+
+@Component({
+ template: `
+
CDK scrollable-table-body with flex table
+
+
+
Material scrollable-table-body with flex table
+
+ `,
+})
+export class ScrollableTableBodyDemo {}
diff --git a/src/material-experimental/config.bzl b/src/material-experimental/config.bzl
index 1006febdc775..eca0e2a4d8e6 100644
--- a/src/material-experimental/config.bzl
+++ b/src/material-experimental/config.bzl
@@ -45,6 +45,7 @@ entryPoints = [
"mdc-tabs/testing",
"menubar",
"popover-edit",
+ "table",
"selection",
]
diff --git a/src/material-experimental/table/BUILD.bazel b/src/material-experimental/table/BUILD.bazel
new file mode 100644
index 000000000000..78b6c67faffc
--- /dev/null
+++ b/src/material-experimental/table/BUILD.bazel
@@ -0,0 +1,19 @@
+load("//tools:defaults.bzl", "ng_module")
+
+package(default_visibility = ["//visibility:public"])
+
+ng_module(
+ name = "table",
+ srcs = glob(
+ ["**/*.ts"],
+ exclude = ["**/*.spec.ts"],
+ ),
+ module_name = "@angular/material-experimental/table",
+ deps = [
+ "//src/material/table",
+ "//src/cdk-experimental/table",
+ "@npm//@angular/common",
+ "@npm//@angular/core",
+ "@npm//rxjs",
+ ],
+)
diff --git a/src/material-experimental/table/index.ts b/src/material-experimental/table/index.ts
new file mode 100644
index 000000000000..676ca90f1ffa
--- /dev/null
+++ b/src/material-experimental/table/index.ts
@@ -0,0 +1,9 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+export * from './public-api';
diff --git a/src/material-experimental/table/public-api.ts b/src/material-experimental/table/public-api.ts
new file mode 100644
index 000000000000..0f0b4879b5b4
--- /dev/null
+++ b/src/material-experimental/table/public-api.ts
@@ -0,0 +1,10 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+export * from './scrollable-table-body-layout';
+export * from './scrollable-table-body-module';
diff --git a/src/material-experimental/table/scrollable-table-body-layout.ts b/src/material-experimental/table/scrollable-table-body-layout.ts
new file mode 100644
index 000000000000..b7c395d94124
--- /dev/null
+++ b/src/material-experimental/table/scrollable-table-body-layout.ts
@@ -0,0 +1,23 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+import {Directive} from '@angular/core';
+import {
+ _TABLE_LAYOUT_STRATEGY,
+} from '@angular/cdk/table/table-layout-strategy';
+import {ScrollableTableBodyLayoutStrategy} from '@angular/cdk-experimental/table';
+
+/** A directive that enables scrollable body content for flex tables. */
+@Directive({
+ selector: 'mat-table[scrollableBody]',
+ providers: [
+ {provide: _TABLE_LAYOUT_STRATEGY, useClass: ScrollableTableBodyLayoutStrategy},
+ ]
+})
+export class MatScrollableTableBody {
+}
diff --git a/src/material-experimental/table/scrollable-table-body-module.ts b/src/material-experimental/table/scrollable-table-body-module.ts
new file mode 100644
index 000000000000..5ae89ecaef40
--- /dev/null
+++ b/src/material-experimental/table/scrollable-table-body-module.ts
@@ -0,0 +1,22 @@
+/**
+ * @license
+ * Copyright Google LLC All Rights Reserved.
+ *
+ * Use of this source code is governed by an MIT-style license that can be
+ * found in the LICENSE file at https://angular.io/license
+ */
+
+import {NgModule} from '@angular/core';
+import {MatScrollableTableBody} from './scrollable-table-body-layout';
+
+export {MatScrollableTableBody};
+
+const EXPORTED_DECLARATIONS = [
+ MatScrollableTableBody,
+];
+
+@NgModule({
+ exports: EXPORTED_DECLARATIONS,
+ declarations: EXPORTED_DECLARATIONS,
+})
+export class MatScrollableTableBodyModule { }