mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
feat(table): add optional footer row (#10330)
This commit is contained in:
+45
-6
@@ -8,12 +8,17 @@
|
||||
|
||||
import {ContentChild, Directive, ElementRef, Input, TemplateRef} from '@angular/core';
|
||||
|
||||
/** Base interface for a cell definition. Captures a column's cell template definition. */
|
||||
export interface CellDef {
|
||||
template: TemplateRef<any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cell definition for a CDK table.
|
||||
* Captures the template of a column's data row cell as well as cell-specific properties.
|
||||
*/
|
||||
@Directive({selector: '[cdkCellDef]'})
|
||||
export class CdkCellDef {
|
||||
export class CdkCellDef implements CellDef {
|
||||
constructor(/** @docs-private */ public template: TemplateRef<any>) { }
|
||||
}
|
||||
|
||||
@@ -22,7 +27,16 @@ export class CdkCellDef {
|
||||
* Captures the template of a column's header cell and as well as cell-specific properties.
|
||||
*/
|
||||
@Directive({selector: '[cdkHeaderCellDef]'})
|
||||
export class CdkHeaderCellDef {
|
||||
export class CdkHeaderCellDef implements CellDef {
|
||||
constructor(/** @docs-private */ public template: TemplateRef<any>) { }
|
||||
}
|
||||
|
||||
/**
|
||||
* Footer cell definition for a CDK table.
|
||||
* Captures the template of a column's footer cell and as well as cell-specific properties.
|
||||
*/
|
||||
@Directive({selector: '[cdkFooterCellDef]'})
|
||||
export class CdkFooterCellDef implements CellDef {
|
||||
constructor(/** @docs-private */ public template: TemplateRef<any>) { }
|
||||
}
|
||||
|
||||
@@ -51,6 +65,9 @@ export class CdkColumnDef {
|
||||
/** @docs-private */
|
||||
@ContentChild(CdkHeaderCellDef) headerCell: CdkHeaderCellDef;
|
||||
|
||||
/** @docs-private */
|
||||
@ContentChild(CdkFooterCellDef) footerCell: CdkFooterCellDef;
|
||||
|
||||
/**
|
||||
* Transformed version of the column name that can be used as part of a CSS classname. Excludes
|
||||
* all non-alphanumeric characters and the special characters '-' and '_'. Any characters that
|
||||
@@ -59,6 +76,14 @@ export class CdkColumnDef {
|
||||
cssClassFriendlyName: string;
|
||||
}
|
||||
|
||||
/** Base class for the cells. Adds a CSS classname that identifies the column it renders in. */
|
||||
export class BaseCdkCell {
|
||||
constructor(columnDef: CdkColumnDef, elementRef: ElementRef) {
|
||||
const columnClassName = `cdk-column-${columnDef.cssClassFriendlyName}`;
|
||||
elementRef.nativeElement.classList.add(columnClassName);
|
||||
}
|
||||
}
|
||||
|
||||
/** Header cell template container that adds the right classes and role. */
|
||||
@Directive({
|
||||
selector: 'cdk-header-cell, th[cdk-header-cell]',
|
||||
@@ -67,9 +92,23 @@ export class CdkColumnDef {
|
||||
'role': 'columnheader',
|
||||
},
|
||||
})
|
||||
export class CdkHeaderCell {
|
||||
export class CdkHeaderCell extends BaseCdkCell {
|
||||
constructor(columnDef: CdkColumnDef, elementRef: ElementRef) {
|
||||
elementRef.nativeElement.classList.add(`cdk-column-${columnDef.cssClassFriendlyName}`);
|
||||
super(columnDef, elementRef);
|
||||
}
|
||||
}
|
||||
|
||||
/** Footer cell template container that adds the right classes and role. */
|
||||
@Directive({
|
||||
selector: 'cdk-footer-cell, td[cdk-footer-cell]',
|
||||
host: {
|
||||
'class': 'cdk-footer-cell',
|
||||
'role': 'gridcell',
|
||||
},
|
||||
})
|
||||
export class CdkFooterCell extends BaseCdkCell {
|
||||
constructor(columnDef: CdkColumnDef, elementRef: ElementRef) {
|
||||
super(columnDef, elementRef);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,8 +120,8 @@ export class CdkHeaderCell {
|
||||
'role': 'gridcell',
|
||||
},
|
||||
})
|
||||
export class CdkCell {
|
||||
export class CdkCell extends BaseCdkCell {
|
||||
constructor(columnDef: CdkColumnDef, elementRef: ElementRef) {
|
||||
elementRef.nativeElement.classList.add(`cdk-column-${columnDef.cssClassFriendlyName}`);
|
||||
super(columnDef, elementRef);
|
||||
}
|
||||
}
|
||||
|
||||
+49
-2
@@ -18,7 +18,7 @@ import {
|
||||
ViewContainerRef,
|
||||
ViewEncapsulation,
|
||||
} from '@angular/core';
|
||||
import {CdkCellDef} from './cell';
|
||||
import {CdkCellDef, CdkColumnDef} from './cell';
|
||||
|
||||
/**
|
||||
* The row template that can be used by the mat-table. Should not be used outside of the
|
||||
@@ -57,6 +57,9 @@ export abstract class BaseRowDef {
|
||||
getColumnsDiff(): IterableChanges<any> | null {
|
||||
return this._columnsDiffer.diff(this.columns);
|
||||
}
|
||||
|
||||
/** Gets this row def's relevant cell template from the provided column def. */
|
||||
abstract extractCellTemplate(column: CdkColumnDef): TemplateRef<any>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -71,6 +74,30 @@ export class CdkHeaderRowDef extends BaseRowDef {
|
||||
constructor(template: TemplateRef<any>, _differs: IterableDiffers) {
|
||||
super(template, _differs);
|
||||
}
|
||||
|
||||
/** Gets this row def's relevant cell template from the provided column def. */
|
||||
extractCellTemplate(column: CdkColumnDef): TemplateRef<any> {
|
||||
return column.headerCell.template;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Footer row definition for the CDK table.
|
||||
* Captures the footer row's template and other footer properties such as the columns to display.
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[cdkFooterRowDef]',
|
||||
inputs: ['columns: cdkFooterRowDef'],
|
||||
})
|
||||
export class CdkFooterRowDef extends BaseRowDef {
|
||||
constructor(template: TemplateRef<any>, _differs: IterableDiffers) {
|
||||
super(template, _differs);
|
||||
}
|
||||
|
||||
/** Gets this row def's relevant cell template from the provided column def. */
|
||||
extractCellTemplate(column: CdkColumnDef): TemplateRef<any> {
|
||||
return column.footerCell.template;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -96,12 +123,17 @@ export class CdkRowDef<T> extends BaseRowDef {
|
||||
constructor(template: TemplateRef<any>, _differs: IterableDiffers) {
|
||||
super(template, _differs);
|
||||
}
|
||||
|
||||
/** Gets this row def's relevant cell template from the provided column def. */
|
||||
extractCellTemplate(column: CdkColumnDef): TemplateRef<any> {
|
||||
return column.cell.template;
|
||||
}
|
||||
}
|
||||
|
||||
/** Context provided to the row cells */
|
||||
export interface CdkCellOutletRowContext<T> {
|
||||
/** Data for the row that this cell is located within. */
|
||||
$implicit: T;
|
||||
$implicit?: T;
|
||||
|
||||
/** Index location of the row that this cell is located within. */
|
||||
index?: number;
|
||||
@@ -162,6 +194,21 @@ export class CdkCellOutlet {
|
||||
})
|
||||
export class CdkHeaderRow { }
|
||||
|
||||
|
||||
/** Footer template container that contains the cell outlet. Adds the right class and role. */
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'cdk-footer-row, tr[cdk-footer-row]',
|
||||
template: CDK_ROW_TEMPLATE,
|
||||
host: {
|
||||
'class': 'cdk-footer-row',
|
||||
'role': 'row',
|
||||
},
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
})
|
||||
export class CdkFooterRow { }
|
||||
|
||||
/** Data row template container that contains the cell outlet. Adds the right class and role. */
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
|
||||
@@ -8,9 +8,15 @@
|
||||
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {NgModule} from '@angular/core';
|
||||
import {CdkTable, HeaderRowPlaceholder, RowPlaceholder} from './table';
|
||||
import {CdkCellOutlet, CdkHeaderRow, CdkHeaderRowDef, CdkRow, CdkRowDef} from './row';
|
||||
import {CdkCell, CdkCellDef, CdkColumnDef, CdkHeaderCell, CdkHeaderCellDef} from './cell';
|
||||
import {HeaderRowOutlet, DataRowOutlet, CdkTable, FooterRowOutlet} from './table';
|
||||
import {
|
||||
CdkCellOutlet, CdkFooterRow, CdkFooterRowDef, CdkHeaderRow, CdkHeaderRowDef, CdkRow,
|
||||
CdkRowDef
|
||||
} from './row';
|
||||
import {
|
||||
CdkColumnDef, CdkHeaderCellDef, CdkHeaderCell, CdkCell, CdkCellDef,
|
||||
CdkFooterCellDef, CdkFooterCell
|
||||
} from './cell';
|
||||
|
||||
const EXPORTED_DECLARATIONS = [
|
||||
CdkTable,
|
||||
@@ -18,14 +24,19 @@ const EXPORTED_DECLARATIONS = [
|
||||
CdkCellDef,
|
||||
CdkCellOutlet,
|
||||
CdkHeaderCellDef,
|
||||
CdkFooterCellDef,
|
||||
CdkColumnDef,
|
||||
CdkCell,
|
||||
CdkRow,
|
||||
CdkHeaderCell,
|
||||
CdkFooterCell,
|
||||
CdkHeaderRow,
|
||||
CdkHeaderRowDef,
|
||||
RowPlaceholder,
|
||||
HeaderRowPlaceholder,
|
||||
CdkFooterRow,
|
||||
CdkFooterRowDef,
|
||||
DataRowOutlet,
|
||||
HeaderRowOutlet,
|
||||
FooterRowOutlet,
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
||||
@@ -18,13 +18,15 @@ top of the CDK data-table.
|
||||
|
||||
The first step to writing the data-table template is to define the columns.
|
||||
A column definition is specified via an `<ng-container>` with the `cdkColumnDef` directive, giving
|
||||
the column a name. Each column definition then further defines both a header-cell template
|
||||
(`cdkHeaderCellDef`) and a data-cell template (`cdkCellDef`).
|
||||
the column a name. Each column definition can contain a header-cell template
|
||||
(`cdkHeaderCellDef`), data-cell template (`cdkCellDef`), and footer-cell
|
||||
template (`cdkFooterCellDef`).
|
||||
|
||||
```html
|
||||
<ng-container cdkColumnDef="username">
|
||||
<th cdk-header-cell *cdkHeaderCellDef> User name </th>
|
||||
<td cdk-cell *cdkCellDef="let row"> {{row.a}} </td>
|
||||
<td cdk-footer-cell *cdkFooterCellDef> User name </td>
|
||||
</ng-container>
|
||||
```
|
||||
|
||||
@@ -35,11 +37,15 @@ Note that `cdkCellDef` exports the row context such that the row data can be ref
|
||||
template. The directive also exports the same properties as `ngFor` (index, even, odd, first,
|
||||
last).
|
||||
|
||||
The next step is to define the table's header-row (`cdkHeaderRowDef`) and data-row (`cdkRowDef`).
|
||||
The next step is to define the table's header-row (`cdkHeaderRowDef`), data-row (`cdkRowDef`),
|
||||
and fitler-row (`cdkFooterRowDef`). Note that each of these are optional to include, depending on
|
||||
what type of rows you want rendered (e.g. if you do not need a footer row, simply do not add
|
||||
its definition).
|
||||
|
||||
```html
|
||||
<tr cdk-header-row *cdkHeaderRowDef="['username', 'age', 'title']"></tr>
|
||||
<tr cdk-row *cdkRowDef="let row; columns: ['username', 'age', 'title']"></tr>
|
||||
<tr cdk-footer-row *cdkFooterRowDef="['username', 'age', 'title']"></tr>
|
||||
```
|
||||
|
||||
These row templates accept the specific columns to be rendered via the name given to the
|
||||
|
||||
+417
-366
File diff suppressed because it is too large
Load Diff
+145
-101
@@ -23,17 +23,24 @@ import {
|
||||
IterableDiffer,
|
||||
IterableDiffers,
|
||||
OnInit,
|
||||
QueryList,
|
||||
QueryList, TemplateRef,
|
||||
TrackByFunction,
|
||||
ViewChild,
|
||||
ViewContainerRef,
|
||||
ViewEncapsulation,
|
||||
} from '@angular/core';
|
||||
import {CollectionViewer, DataSource} from '@angular/cdk/collections';
|
||||
import {CdkCellOutlet, CdkCellOutletRowContext, CdkHeaderRowDef, CdkRowDef} from './row';
|
||||
import {
|
||||
BaseRowDef,
|
||||
CdkCellOutlet,
|
||||
CdkCellOutletRowContext,
|
||||
CdkFooterRowDef,
|
||||
CdkHeaderRowDef,
|
||||
CdkRowDef
|
||||
} from './row';
|
||||
import {takeUntil} from 'rxjs/operators';
|
||||
import {of as observableOf, BehaviorSubject, Observable, Subscription, Subject} from 'rxjs';
|
||||
import {CdkCellDef, CdkColumnDef, CdkHeaderCellDef} from './cell';
|
||||
import {CdkColumnDef} from './cell';
|
||||
import {
|
||||
getTableDuplicateColumnNameError,
|
||||
getTableMissingMatchingRowDefError,
|
||||
@@ -43,12 +50,17 @@ import {
|
||||
getTableUnknownDataSourceError
|
||||
} from './table-errors';
|
||||
|
||||
/** Interface used to provide an outlet for rows to be inserted into. */
|
||||
export interface RowOutlet {
|
||||
viewContainer: ViewContainerRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a handle for the table to grab the view container's ng-container to insert data rows.
|
||||
* @docs-private
|
||||
*/
|
||||
@Directive({selector: '[rowPlaceholder]'})
|
||||
export class RowPlaceholder {
|
||||
@Directive({selector: '[rowOutlet]'})
|
||||
export class DataRowOutlet implements RowOutlet {
|
||||
constructor(public viewContainer: ViewContainerRef, public elementRef: ElementRef) { }
|
||||
}
|
||||
|
||||
@@ -56,18 +68,29 @@ export class RowPlaceholder {
|
||||
* Provides a handle for the table to grab the view container's ng-container to insert the header.
|
||||
* @docs-private
|
||||
*/
|
||||
@Directive({selector: '[headerRowPlaceholder]'})
|
||||
export class HeaderRowPlaceholder {
|
||||
@Directive({selector: '[headerRowOutlet]'})
|
||||
export class HeaderRowOutlet implements RowOutlet {
|
||||
constructor(public viewContainer: ViewContainerRef, public elementRef: ElementRef) { }
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a handle for the table to grab the view container's ng-container to insert the footer.
|
||||
* @docs-private
|
||||
*/
|
||||
@Directive({selector: '[footerRowOutlet]'})
|
||||
export class FooterRowOutlet implements RowOutlet {
|
||||
constructor(public viewContainer: ViewContainerRef, public elementRef: ElementRef) { }
|
||||
}
|
||||
|
||||
/**
|
||||
* The table template that can be used by the mat-table. Should not be used outside of the
|
||||
* material library.
|
||||
* @docs-private
|
||||
*/
|
||||
export const CDK_TABLE_TEMPLATE = `
|
||||
<ng-container headerRowPlaceholder></ng-container>
|
||||
<ng-container rowPlaceholder></ng-container>`;
|
||||
<ng-container headerRowOutlet></ng-container>
|
||||
<ng-container rowOutlet></ng-container>
|
||||
<ng-container footerRowOutlet></ng-container>`;
|
||||
|
||||
/**
|
||||
* Class used to conveniently type the embedded view ref for rows with a context.
|
||||
@@ -76,10 +99,10 @@ export const CDK_TABLE_TEMPLATE = `
|
||||
abstract class RowViewRef<T> extends EmbeddedViewRef<CdkCellOutletRowContext<T>> { }
|
||||
|
||||
/**
|
||||
* A data table that renders a header row and data rows. Uses the dataSource input to determine
|
||||
* the data to be rendered. The data can be provided either as a data array, an Observable stream
|
||||
* that emits the data array to render, or a DataSource with a connect function that will
|
||||
* return an Observable stream that emits the data array to render.
|
||||
* A data table that can render a header row, data rows, and a footer row.
|
||||
* Uses the dataSource input to determine the data to be rendered. The data can be provided either
|
||||
* as a data array, an Observable stream that emits the data array to render, or a DataSource with a
|
||||
* connect function that will return an Observable stream that emits the data array to render.
|
||||
*/
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
@@ -103,9 +126,9 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke
|
||||
private _renderChangeSubscription: Subscription | null;
|
||||
|
||||
/**
|
||||
* Map of all the user's defined columns (header and data cell template) identified by name.
|
||||
* Collection populated by the column definitions gathered by `ContentChildren` as well as any
|
||||
* custom column definitions added to `_customColumnDefs`.
|
||||
* Map of all the user's defined columns (header, data, and footer cell template) identified by
|
||||
* name. Collection populated by the column definitions gathered by `ContentChildren` as well as
|
||||
* any custom column definitions added to `_customColumnDefs`.
|
||||
*/
|
||||
private _columnDefsByName = new Map<string, CdkColumnDef>();
|
||||
|
||||
@@ -133,6 +156,12 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke
|
||||
*/
|
||||
private _headerRowDefChanged = false;
|
||||
|
||||
/**
|
||||
* Whether the footer row definition has been changed. Triggers an update to the footer row after
|
||||
* content is checked.
|
||||
*/
|
||||
private _footerRowDefChanged = false;
|
||||
|
||||
/**
|
||||
* Tracking function that will be used to check the differences in data changes. Used similarly
|
||||
* to `ngFor` `trackBy` function. Optimize row operations by identifying a row based on its data
|
||||
@@ -178,7 +207,7 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke
|
||||
this._switchDataSource(dataSource);
|
||||
}
|
||||
}
|
||||
private _dataSource: DataSource<T> | Observable<T[]> | T[];
|
||||
private _dataSource: DataSource<T> | Observable<T[]> | T[] | T[];
|
||||
|
||||
// TODO(andrewseguin): Remove max value as the end index
|
||||
// and instead calculate the view on init and scroll.
|
||||
@@ -189,13 +218,14 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke
|
||||
viewChange: BehaviorSubject<{start: number, end: number}> =
|
||||
new BehaviorSubject<{start: number, end: number}>({start: 0, end: Number.MAX_VALUE});
|
||||
|
||||
// Placeholders within the table's template where the header and data rows will be inserted.
|
||||
@ViewChild(RowPlaceholder) _rowPlaceholder: RowPlaceholder;
|
||||
@ViewChild(HeaderRowPlaceholder) _headerRowPlaceholder: HeaderRowPlaceholder;
|
||||
// Outlets in the table's template where the header, data rows, and footer will be inserted.
|
||||
@ViewChild(DataRowOutlet) _rowOutlet: DataRowOutlet;
|
||||
@ViewChild(HeaderRowOutlet) _headerRowOutlet: HeaderRowOutlet;
|
||||
@ViewChild(FooterRowOutlet) _footerRowOutlet: FooterRowOutlet;
|
||||
|
||||
/**
|
||||
* The column definitions provided by the user that contain what the header and cells should
|
||||
* render for each column.
|
||||
* The column definitions provided by the user that contain what the header, data, and footer
|
||||
* cells should render for each column.
|
||||
*/
|
||||
@ContentChildren(CdkColumnDef) _contentColumnDefs: QueryList<CdkColumnDef>;
|
||||
|
||||
@@ -210,6 +240,14 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke
|
||||
*/
|
||||
@ContentChild(CdkHeaderRowDef) _headerRowDef: CdkHeaderRowDef;
|
||||
|
||||
/**
|
||||
* Template definition used as the footer container. By default it stores the footer row
|
||||
* definition found as a direct content child. Override this value through `setFooterRowDef` if
|
||||
* the footer row definition should be changed or was not defined as a part of the table's
|
||||
* content.
|
||||
*/
|
||||
@ContentChild(CdkFooterRowDef) _footerRowDef: CdkFooterRowDef;
|
||||
|
||||
constructor(private readonly _differs: IterableDiffers,
|
||||
private readonly _changeDetectorRef: ChangeDetectorRef,
|
||||
private readonly _elementRef: ElementRef,
|
||||
@@ -227,11 +265,10 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke
|
||||
// TODO(andrewseguin): Setup a listener for scrolling, emit the calculated view to viewChange
|
||||
this._dataDiffer = this._differs.find([]).create(this._trackByFn);
|
||||
|
||||
// If the table has a header row definition defined as part of its content, flag this as a
|
||||
// header row def change so that the content check will render the header row.
|
||||
if (this._headerRowDef) {
|
||||
this._headerRowDefChanged = true;
|
||||
}
|
||||
// If the table has header or footer row definitions defined as part of its content, mark that
|
||||
// there is a change so that the content check will render the row.
|
||||
this._headerRowDefChanged = !!this._headerRowDef;
|
||||
this._footerRowDefChanged = !!this._footerRowDef;
|
||||
}
|
||||
|
||||
ngAfterContentChecked() {
|
||||
@@ -239,12 +276,12 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke
|
||||
this._cacheRowDefs();
|
||||
this._cacheColumnDefs();
|
||||
|
||||
// Make sure that the user has at least added a header row or row def.
|
||||
if (!this._headerRowDef && !this._rowDefs.length) {
|
||||
// Make sure that the user has at least added header, footer, or data row def.
|
||||
if (!this._headerRowDef && !this._footerRowDef && !this._rowDefs.length) {
|
||||
throw getTableMissingRowDefsError();
|
||||
}
|
||||
|
||||
// Render updates if the list of columns have been changed for the header or row definitions.
|
||||
// Render updates if the list of columns have been changed for the header, row, or footer defs.
|
||||
this._renderUpdatedColumns();
|
||||
|
||||
// If the header row definition has been changed, trigger a render to the header row.
|
||||
@@ -253,6 +290,12 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke
|
||||
this._headerRowDefChanged = false;
|
||||
}
|
||||
|
||||
// If the footer row definition has been changed, trigger a render to the footer row.
|
||||
if (this._footerRowDefChanged) {
|
||||
this._renderFooterRow();
|
||||
this._footerRowDefChanged = false;
|
||||
}
|
||||
|
||||
// If there is a data source and row definitions, connect to the data source unless a
|
||||
// connection has already been made.
|
||||
if (this.dataSource && this._rowDefs.length > 0 && !this._renderChangeSubscription) {
|
||||
@@ -261,8 +304,9 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this._rowPlaceholder.viewContainer.clear();
|
||||
this._headerRowPlaceholder.viewContainer.clear();
|
||||
this._rowOutlet.viewContainer.clear();
|
||||
this._headerRowOutlet.viewContainer.clear();
|
||||
this._footerRowOutlet.viewContainer.clear();
|
||||
this._onDestroy.next();
|
||||
this._onDestroy.complete();
|
||||
|
||||
@@ -285,7 +329,7 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke
|
||||
const changes = this._dataDiffer.diff(this._data);
|
||||
if (!changes) { return; }
|
||||
|
||||
const viewContainer = this._rowPlaceholder.viewContainer;
|
||||
const viewContainer = this._rowOutlet.viewContainer;
|
||||
changes.forEachOperation(
|
||||
(record: IterableChangeRecord<T>, adjustedPreviousIndex: number, currentIndex: number) => {
|
||||
if (record.previousIndex == null) {
|
||||
@@ -319,6 +363,16 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke
|
||||
this._headerRowDefChanged = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the footer row definition to be used. Overrides the footer row definition gathered by
|
||||
* using `ContentChild`, if one exists. Sets a flag that will re-render the footer row after the
|
||||
* table's content is checked.
|
||||
*/
|
||||
setFooterRowDef(footerRowDef: CdkFooterRowDef) {
|
||||
this._footerRowDef = footerRowDef;
|
||||
this._footerRowDefChanged = true;
|
||||
}
|
||||
|
||||
/** Adds a column definition that was not included as part of the direct content children. */
|
||||
addColumnDef(columnDef: CdkColumnDef) {
|
||||
this._customColumnDefs.add(columnDef);
|
||||
@@ -365,8 +419,8 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the header or rows have changed what columns they want to display. If there is a diff,
|
||||
* then re-render that section.
|
||||
* Check if the header, data, or footer rows have changed what columns they want to display.
|
||||
* If there is a diff, then re-render that section.
|
||||
*/
|
||||
private _renderUpdatedColumns() {
|
||||
// Re-render the rows when the row definition columns change.
|
||||
@@ -375,7 +429,7 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke
|
||||
// Reset the data to an empty array so that renderRowChanges will re-render all new rows.
|
||||
this._dataDiffer.diff([]);
|
||||
|
||||
this._rowPlaceholder.viewContainer.clear();
|
||||
this._rowOutlet.viewContainer.clear();
|
||||
this.renderRows();
|
||||
}
|
||||
});
|
||||
@@ -384,12 +438,17 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke
|
||||
if (this._headerRowDef && this._headerRowDef.getColumnsDiff()) {
|
||||
this._renderHeaderRow();
|
||||
}
|
||||
|
||||
// Re-render the footer row if there is a difference in its columns.
|
||||
if (this._footerRowDef && this._footerRowDef.getColumnsDiff()) {
|
||||
this._renderFooterRow();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch to the provided data source by resetting the data and unsubscribing from the current
|
||||
* render change subscription if one exists. If the data source is null, interpret this by
|
||||
* clearing the row placeholder. Otherwise start listening for new data.
|
||||
* clearing the row outlet. Otherwise start listening for new data.
|
||||
*/
|
||||
private _switchDataSource(dataSource: DataSource<T> | Observable<T[]> | T[]) {
|
||||
this._data = [];
|
||||
@@ -408,7 +467,7 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke
|
||||
if (this._dataDiffer) {
|
||||
this._dataDiffer.diff([]);
|
||||
}
|
||||
this._rowPlaceholder.viewContainer.clear();
|
||||
this._rowOutlet.viewContainer.clear();
|
||||
}
|
||||
|
||||
this._dataSource = dataSource;
|
||||
@@ -446,31 +505,29 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears any existing content in the header row placeholder and creates a new embedded view
|
||||
* in the placeholder using the header row definition.
|
||||
* Clears any existing content in the header row outlet and creates a new embedded view
|
||||
* in the outlet using the header row definition.
|
||||
*/
|
||||
private _renderHeaderRow() {
|
||||
// Clear the header row placeholder if any content exists.
|
||||
if (this._headerRowPlaceholder.viewContainer.length > 0) {
|
||||
this._headerRowPlaceholder.viewContainer.clear();
|
||||
// Clear the footer row outlet if any content exists.
|
||||
if (this._headerRowOutlet.viewContainer.length > 0) {
|
||||
this._headerRowOutlet.viewContainer.clear();
|
||||
}
|
||||
|
||||
const cells = this._getHeaderCellTemplatesForRow(this._headerRowDef);
|
||||
if (!cells.length) { return; }
|
||||
this._renderRow(this._headerRowOutlet, this._headerRowDef);
|
||||
}
|
||||
|
||||
// TODO(andrewseguin): add some code to enforce that exactly
|
||||
// one CdkCellOutlet was instantiated as a result
|
||||
// of `createEmbeddedView`.
|
||||
this._headerRowPlaceholder.viewContainer
|
||||
.createEmbeddedView(this._headerRowDef.template, {cells});
|
||||
/**
|
||||
* Clears any existing content in the footer row outlet and creates a new embedded view
|
||||
* in the outlet using the footer row definition.
|
||||
*/
|
||||
private _renderFooterRow() {
|
||||
// Clear the footer row outlet if any content exists.
|
||||
if (this._footerRowOutlet.viewContainer.length > 0) {
|
||||
this._footerRowOutlet.viewContainer.clear();
|
||||
}
|
||||
|
||||
cells.forEach(cell => {
|
||||
if (CdkCellOutlet.mostRecentCellOutlet) {
|
||||
CdkCellOutlet.mostRecentCellOutlet._viewContainer.createEmbeddedView(cell.template, {});
|
||||
}
|
||||
});
|
||||
|
||||
this._changeDetectorRef.markForCheck();
|
||||
this._renderRow(this._footerRowOutlet, this._footerRowDef);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -493,21 +550,26 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke
|
||||
* within the data row view container.
|
||||
*/
|
||||
private _insertRow(rowData: T, index: number) {
|
||||
const row = this._getRowDef(rowData, index);
|
||||
|
||||
// Row context that will be provided to both the created embedded row view and its cells.
|
||||
const rowDef = this._getRowDef(rowData, index);
|
||||
const context: CdkCellOutletRowContext<T> = {$implicit: rowData};
|
||||
this._renderRow(this._rowOutlet, rowDef, context, index);
|
||||
}
|
||||
|
||||
// TODO(andrewseguin): add some code to enforce that exactly one
|
||||
// CdkCellOutlet was instantiated as a result of `createEmbeddedView`.
|
||||
this._rowPlaceholder.viewContainer.createEmbeddedView(row.template, context, index);
|
||||
/**
|
||||
* Creates a new row template in the outlet and fills it with the set of cell templates.
|
||||
* Optionally takes a context to provide to the row and cells, as well as an optional index
|
||||
* of where to place the new row template in the outlet.
|
||||
*/
|
||||
private _renderRow(
|
||||
outlet: RowOutlet, rowDef: BaseRowDef, context: CdkCellOutletRowContext<T> = {}, index = 0) {
|
||||
// TODO(andrewseguin): enforce that one outlet was instantiated from createEmbeddedView
|
||||
outlet.viewContainer.createEmbeddedView(rowDef.template, context, index);
|
||||
|
||||
this._getCellTemplatesForRow(row).forEach(cell => {
|
||||
for (let cellTemplate of this._getCellTemplates(rowDef)) {
|
||||
if (CdkCellOutlet.mostRecentCellOutlet) {
|
||||
CdkCellOutlet.mostRecentCellOutlet._viewContainer
|
||||
.createEmbeddedView(cell.template, context);
|
||||
CdkCellOutlet.mostRecentCellOutlet._viewContainer.createEmbeddedView(cellTemplate, context);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this._changeDetectorRef.markForCheck();
|
||||
}
|
||||
@@ -517,7 +579,7 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke
|
||||
* e.g. first/last/even/odd.
|
||||
*/
|
||||
private _updateRowIndexContext() {
|
||||
const viewContainer = this._rowPlaceholder.viewContainer;
|
||||
const viewContainer = this._rowOutlet.viewContainer;
|
||||
for (let index = 0, count = viewContainer.length; index < count; index++) {
|
||||
const viewRef = viewContainer.get(index) as RowViewRef<T>;
|
||||
viewRef.context.index = index;
|
||||
@@ -529,30 +591,9 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cell template definitions to insert into the header
|
||||
* as defined by its list of columns to display.
|
||||
*/
|
||||
private _getHeaderCellTemplatesForRow(headerDef: CdkHeaderRowDef): CdkHeaderCellDef[] {
|
||||
if (!headerDef || !headerDef.columns) { return []; }
|
||||
return Array.from(headerDef.columns, columnId => {
|
||||
const column = this._columnDefsByName.get(columnId);
|
||||
|
||||
if (!column) {
|
||||
throw getTableUnknownColumnError(columnId);
|
||||
}
|
||||
|
||||
return column.headerCell;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the cell template definitions to insert in the provided row
|
||||
* as defined by its list of columns to display.
|
||||
*/
|
||||
private _getCellTemplatesForRow(rowDef: CdkRowDef<T>): CdkCellDef[] {
|
||||
if (!rowDef.columns) { return []; }
|
||||
/** Gets the column definitions for the provided row def. */
|
||||
private _getCellTemplates(rowDef: BaseRowDef): TemplateRef<any>[] {
|
||||
if (!rowDef || !rowDef.columns) { return []; }
|
||||
return Array.from(rowDef.columns, columnId => {
|
||||
const column = this._columnDefsByName.get(columnId);
|
||||
|
||||
@@ -560,19 +601,22 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke
|
||||
throw getTableUnknownColumnError(columnId);
|
||||
}
|
||||
|
||||
return column.cell;
|
||||
return rowDef.extractCellTemplate(column);
|
||||
});
|
||||
}
|
||||
|
||||
/** Adds native table sections (e.g. tbody) and moves the row placeholders into them. */
|
||||
/** Adds native table sections (e.g. tbody) and moves the row outlets into them. */
|
||||
private _applyNativeTableSections() {
|
||||
const thead = document.createElement('thead');
|
||||
const tbody = document.createElement('tbody');
|
||||
const sections = [
|
||||
{tag: 'thead', outlet: this._headerRowOutlet},
|
||||
{tag: 'tbody', outlet: this._rowOutlet},
|
||||
{tag: 'tfoot', outlet: this._footerRowOutlet},
|
||||
];
|
||||
|
||||
this._elementRef.nativeElement.appendChild(thead);
|
||||
this._elementRef.nativeElement.appendChild(tbody);
|
||||
|
||||
thead.appendChild(this._headerRowPlaceholder.elementRef.nativeElement);
|
||||
tbody.appendChild(this._rowPlaceholder.elementRef.nativeElement);
|
||||
for (const section of sections) {
|
||||
const element = document.createElement(section.tag);
|
||||
element.appendChild(section.outlet.elementRef.nativeElement);
|
||||
this._elementRef.nativeElement.appendChild(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,12 @@
|
||||
<ng-container matColumnDef="{{column}}" *ngFor="let column of columns">
|
||||
<th mat-header-cell *matHeaderCellDef> {{column}} </th>
|
||||
<td mat-cell *matCellDef="let element"> {{element[column]}} </td>
|
||||
<td mat-footer-cell *matFooterCellDef> Footer {{column}}</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="columns"></tr>
|
||||
<tr mat-row *matRowDef="let data; columns: columns;"></tr>
|
||||
<tr mat-footer-row *matFooterRowDef="columns;"></tr>
|
||||
</table>
|
||||
</mat-card>
|
||||
|
||||
@@ -17,10 +19,12 @@
|
||||
<ng-container matColumnDef="{{column}}" *ngFor="let column of columns">
|
||||
<mat-header-cell *matHeaderCellDef> {{column}} </mat-header-cell>
|
||||
<mat-cell *matCellDef="let element"> {{element[column]}} </mat-cell>
|
||||
<mat-footer-cell *matFooterCellDef> Footer {{column}}</mat-footer-cell>
|
||||
</ng-container>
|
||||
|
||||
<mat-header-row *matHeaderRowDef="columns"></mat-header-row>
|
||||
<mat-row *matRowDef="let data; columns: columns;"></mat-row>
|
||||
<mat-footer-row *matFooterRowDef="columns;"></mat-footer-row>>
|
||||
</mat-table>
|
||||
</mat-card>
|
||||
|
||||
@@ -30,10 +34,12 @@
|
||||
<ng-container cdkColumnDef="{{column}}" *ngFor="let column of columns">
|
||||
<th cdk-header-cell *cdkHeaderCellDef> {{column}} </th>
|
||||
<td cdk-cell *cdkCellDef="let element"> {{element[column]}} </td>
|
||||
<td cdk-footer-cell *cdkFooterCellDef> Footer {{column}}</td>
|
||||
</ng-container>
|
||||
|
||||
<tr cdk-header-row *cdkHeaderRowDef="columns"></tr>
|
||||
<tr cdk-row *cdkRowDef="let data; columns: columns;"></tr>
|
||||
<tr cdk-footer-row *cdkFooterRowDef="columns;"></tr>
|
||||
</table>
|
||||
</mat-card>
|
||||
|
||||
@@ -43,9 +49,11 @@
|
||||
<ng-container cdkColumnDef="{{column}}" *ngFor="let column of columns">
|
||||
<cdk-header-cell *cdkHeaderCellDef> {{column}} </cdk-header-cell>
|
||||
<cdk-cell *cdkCellDef="let element"> {{element[column]}} </cdk-cell>
|
||||
<cdk-footer-cell *cdkFooterCellDef> Footer {{column}}</cdk-footer-cell>
|
||||
</ng-container>
|
||||
|
||||
<cdk-header-row *cdkHeaderRowDef="columns"></cdk-header-row>
|
||||
<cdk-row *cdkRowDef="let data; columns: columns;"></cdk-row>
|
||||
<cdk-footer-row *cdkFooterRowDef="columns;"></cdk-footer-row>>
|
||||
</cdk-table>
|
||||
</mat-card>
|
||||
@@ -10,8 +10,8 @@
|
||||
background: mat-color($background, 'card');
|
||||
}
|
||||
|
||||
|
||||
mat-row, mat-header-row, th.mat-header-cell, td.mat-cell {
|
||||
mat-row, mat-header-row, mat-footer-row,
|
||||
th.mat-header-cell, td.mat-cell, td.mat-footer-cell {
|
||||
border-bottom-color: mat-color($foreground, divider);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
color: mat-color($foreground, secondary-text);
|
||||
}
|
||||
|
||||
.mat-cell {
|
||||
.mat-cell, .mat-footer-cell {
|
||||
color: mat-color($foreground, text);
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@
|
||||
font-weight: mat-font-weight($config, body-2);
|
||||
}
|
||||
|
||||
.mat-cell {
|
||||
.mat-cell, .mat-footer-cell {
|
||||
font-size: mat-font-size($config, body-1);
|
||||
}
|
||||
}
|
||||
|
||||
+27
-1
@@ -10,7 +10,7 @@ import {Directive, ElementRef, Input} from '@angular/core';
|
||||
import {
|
||||
CdkCell,
|
||||
CdkCellDef,
|
||||
CdkColumnDef,
|
||||
CdkColumnDef, CdkFooterCell, CdkFooterCellDef,
|
||||
CdkHeaderCell,
|
||||
CdkHeaderCellDef,
|
||||
} from '@angular/cdk/table';
|
||||
@@ -35,6 +35,16 @@ export class MatCellDef extends CdkCellDef { }
|
||||
})
|
||||
export class MatHeaderCellDef extends CdkHeaderCellDef { }
|
||||
|
||||
/**
|
||||
* Footer cell definition for the mat-table.
|
||||
* Captures the template of a column's footer cell and as well as cell-specific properties.
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[matFooterCellDef]',
|
||||
providers: [{provide: CdkFooterCellDef, useExisting: MatFooterCellDef}]
|
||||
})
|
||||
export class MatFooterCellDef extends CdkFooterCellDef { }
|
||||
|
||||
/**
|
||||
* Column definition for the mat-table.
|
||||
* Defines a set of cells available for a table column.
|
||||
@@ -64,6 +74,22 @@ export class MatHeaderCell extends CdkHeaderCell {
|
||||
}
|
||||
}
|
||||
|
||||
/** Footer cell template container that adds the right classes and role. */
|
||||
@Directive({
|
||||
selector: 'mat-footer-cell, td[mat-footer-cell]',
|
||||
host: {
|
||||
'class': 'mat-footer-cell',
|
||||
'role': 'gridcell',
|
||||
},
|
||||
})
|
||||
export class MatFooterCell extends CdkFooterCell {
|
||||
constructor(columnDef: CdkColumnDef,
|
||||
elementRef: ElementRef) {
|
||||
super(columnDef, elementRef);
|
||||
elementRef.nativeElement.classList.add(`mat-column-${columnDef.cssClassFriendlyName}`);
|
||||
}
|
||||
}
|
||||
|
||||
/** Cell template container that adds the right classes and role. */
|
||||
@Directive({
|
||||
selector: 'mat-cell, td[mat-cell]',
|
||||
|
||||
+29
-3
@@ -8,7 +8,7 @@
|
||||
|
||||
import {ChangeDetectionStrategy, Component, Directive, ViewEncapsulation} from '@angular/core';
|
||||
import {
|
||||
CDK_ROW_TEMPLATE,
|
||||
CDK_ROW_TEMPLATE, CdkFooterRow, CdkFooterRowDef,
|
||||
CdkHeaderRow,
|
||||
CdkHeaderRowDef,
|
||||
CdkRow,
|
||||
@@ -26,9 +26,20 @@ import {
|
||||
})
|
||||
export class MatHeaderRowDef extends CdkHeaderRowDef { }
|
||||
|
||||
/**
|
||||
* Footer row definition for the mat-table.
|
||||
* Captures the footer row's template and other footer properties such as the columns to display.
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[matFooterRowDef]',
|
||||
providers: [{provide: CdkFooterRowDef, useExisting: MatFooterRowDef}],
|
||||
inputs: ['columns: matFooterRowDef'],
|
||||
})
|
||||
export class MatFooterRowDef extends CdkFooterRowDef { }
|
||||
|
||||
/**
|
||||
* Data row definition for the mat-table.
|
||||
* Captures the header row's template and other row properties such as the columns to display and
|
||||
* Captures the footer row's template and other footer properties such as the columns to display and
|
||||
* a when predicate that describes when this row should be used.
|
||||
*/
|
||||
@Directive({
|
||||
@@ -39,7 +50,7 @@ export class MatHeaderRowDef extends CdkHeaderRowDef { }
|
||||
export class MatRowDef<T> extends CdkRowDef<T> {
|
||||
}
|
||||
|
||||
/** Header template container that contains the cell outlet. Adds the right class and role. */
|
||||
/** Footer template container that contains the cell outlet. Adds the right class and role. */
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'mat-header-row, tr[mat-header-row]',
|
||||
@@ -54,6 +65,21 @@ export class MatRowDef<T> extends CdkRowDef<T> {
|
||||
})
|
||||
export class MatHeaderRow extends CdkHeaderRow { }
|
||||
|
||||
/** Footer template container that contains the cell outlet. Adds the right class and role. */
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'mat-footer-row, tr[mat-footer-row]',
|
||||
template: CDK_ROW_TEMPLATE,
|
||||
host: {
|
||||
'class': 'mat-footer-row',
|
||||
'role': 'row',
|
||||
},
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
exportAs: 'matFooterRow',
|
||||
})
|
||||
export class MatFooterRow extends CdkFooterRow { }
|
||||
|
||||
/** Data row template container that contains the cell outlet. Adds the right class and role. */
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
|
||||
@@ -9,8 +9,23 @@
|
||||
import {NgModule} from '@angular/core';
|
||||
import {MatTable} from './table';
|
||||
import {CdkTableModule} from '@angular/cdk/table';
|
||||
import {MatCell, MatCellDef, MatColumnDef, MatHeaderCell, MatHeaderCellDef} from './cell';
|
||||
import {MatHeaderRow, MatHeaderRowDef, MatRow, MatRowDef} from './row';
|
||||
import {
|
||||
MatCell,
|
||||
MatCellDef,
|
||||
MatColumnDef,
|
||||
MatFooterCell,
|
||||
MatFooterCellDef,
|
||||
MatHeaderCell,
|
||||
MatHeaderCellDef
|
||||
} from './cell';
|
||||
import {
|
||||
MatFooterRow,
|
||||
MatFooterRowDef,
|
||||
MatHeaderRow,
|
||||
MatHeaderRowDef,
|
||||
MatRow,
|
||||
MatRowDef
|
||||
} from './row';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {MatCommonModule} from '@angular/material/core';
|
||||
|
||||
@@ -19,19 +34,23 @@ const EXPORTED_DECLARATIONS = [
|
||||
MatTable,
|
||||
|
||||
// Template defs
|
||||
MatCellDef,
|
||||
MatHeaderCellDef,
|
||||
MatColumnDef,
|
||||
MatHeaderRowDef,
|
||||
MatColumnDef,
|
||||
MatCellDef,
|
||||
MatRowDef,
|
||||
MatFooterCellDef,
|
||||
MatFooterRowDef,
|
||||
|
||||
// Cell directives
|
||||
MatHeaderCell,
|
||||
MatCell,
|
||||
MatFooterCell,
|
||||
|
||||
// Row directions
|
||||
MatHeaderRow,
|
||||
MatRow,
|
||||
MatFooterRow,
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
||||
@@ -257,6 +257,28 @@ masterToggle() {
|
||||
}
|
||||
```
|
||||
|
||||
#### Footer row
|
||||
|
||||
A footer row can be added to the table by adding a footer row definition to the table and adding
|
||||
footer cell templates to column definitions. The footer row will be rendered after the rendered
|
||||
data rows.
|
||||
|
||||
```html
|
||||
<ng-container matColumnDef="cost">
|
||||
<th mat-header-cell *matHeaderCellDef> Cost </th>
|
||||
<td mat-cell *matCellDef="let data"> {{data.cost}} </td>
|
||||
<td mat-footer-cell *matFooterCellDef> {{totalCost}} </td>
|
||||
</ng-container>
|
||||
|
||||
...
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="columnsToDisplay"></tr>
|
||||
<tr mat-row *matRowDef="let myRowData; columns: columnsToDisplay"></tr>
|
||||
<tr mat-footer-row *matFooterRowDef="columnsToDisplay"></tr
|
||||
```
|
||||
|
||||
<!--- example(table-footer-row) -->
|
||||
|
||||
##### 4. Include overflow styling
|
||||
|
||||
Finally, adjust the styling for the select column so that its overflow is not hidden. This allows
|
||||
|
||||
+13
-10
@@ -13,14 +13,17 @@ mat-header-row {
|
||||
min-height: $mat-header-row-height;
|
||||
}
|
||||
|
||||
mat-row {
|
||||
mat-row, mat-footer-row {
|
||||
min-height: $mat-row-height;
|
||||
}
|
||||
|
||||
mat-row, mat-header-row {
|
||||
mat-row, mat-header-row, mat-footer-row {
|
||||
display: flex;
|
||||
// Define a border style, but then widths default to 3px. Reset them to 0px except the bottom
|
||||
// which should be 1px;
|
||||
border-width: 0;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
border-style: solid;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
|
||||
@@ -34,7 +37,7 @@ mat-row, mat-header-row {
|
||||
}
|
||||
}
|
||||
|
||||
mat-cell:first-child, mat-header-cell:first-child {
|
||||
mat-cell:first-child, mat-header-cell:first-child, mat-footer-cell:first-child {
|
||||
padding-left: $mat-row-horizontal-padding;
|
||||
|
||||
[dir='rtl'] & {
|
||||
@@ -43,7 +46,7 @@ mat-cell:first-child, mat-header-cell:first-child {
|
||||
}
|
||||
}
|
||||
|
||||
mat-cell:last-child, mat-header-cell:last-child {
|
||||
mat-cell:last-child, mat-header-cell:last-child, mat-footer-cell:last-child {
|
||||
padding-right: $mat-row-horizontal-padding;
|
||||
|
||||
[dir='rtl'] & {
|
||||
@@ -52,7 +55,7 @@ mat-cell:last-child, mat-header-cell:last-child {
|
||||
}
|
||||
}
|
||||
|
||||
mat-cell, mat-header-cell {
|
||||
mat-cell, mat-header-cell, mat-footer-cell {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -72,7 +75,7 @@ tr.mat-header-row {
|
||||
height: $mat-header-row-height;
|
||||
}
|
||||
|
||||
tr.mat-row {
|
||||
tr.mat-row, tr.mat-footer-row {
|
||||
height: $mat-row-height;
|
||||
}
|
||||
|
||||
@@ -80,16 +83,16 @@ th.mat-header-cell {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
th.mat-header-cell, td.mat-cell {
|
||||
th.mat-header-cell, td.mat-cell, td.mat-footer-cell {
|
||||
padding: 0;
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
}
|
||||
|
||||
th.mat-header-cell:first-child, td.mat-cell:first-child {
|
||||
th.mat-header-cell:first-child, td.mat-cell:first-child, td.mat-footer-cell:first-child {
|
||||
padding-left: $mat-row-horizontal-padding;
|
||||
}
|
||||
|
||||
th.mat-header-cell:last-child, td.mat-cell:last-child {
|
||||
th.mat-header-cell:last-child, td.mat-cell:last-child, td.mat-footer-cell:last-child {
|
||||
padding-right: $mat-row-horizontal-padding;
|
||||
}
|
||||
|
||||
+71
-27
@@ -37,7 +37,8 @@ describe('MatTable', () => {
|
||||
[data[0].a, data[0].b, data[0].c],
|
||||
[data[1].a, data[1].b, data[1].c],
|
||||
[data[2].a, data[2].b, data[2].c],
|
||||
['fourth_row']
|
||||
['fourth_row'],
|
||||
['Footer A', 'Footer B', 'Footer C'],
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -51,7 +52,8 @@ describe('MatTable', () => {
|
||||
['a_1'],
|
||||
['a_2'],
|
||||
['a_3'],
|
||||
['fourth_row']
|
||||
['fourth_row'],
|
||||
['Footer A', 'Footer B', 'Footer C'],
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -120,6 +122,7 @@ describe('MatTable', () => {
|
||||
['a_1', 'b_1', 'c_1'],
|
||||
['a_2', 'b_2', 'c_2'],
|
||||
['a_3', 'b_3', 'c_3'],
|
||||
['Footer A', 'Footer B', 'Footer C'],
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -133,6 +136,7 @@ describe('MatTable', () => {
|
||||
['a_2', 'b_2', 'c_2'],
|
||||
['a_3', 'b_3', 'c_3'],
|
||||
['a_4', 'b_4', 'c_4'],
|
||||
['Footer A', 'Footer B', 'Footer C'],
|
||||
]);
|
||||
|
||||
// Remove data
|
||||
@@ -145,6 +149,7 @@ describe('MatTable', () => {
|
||||
['a_2', 'b_2', 'c_2'],
|
||||
['a_3', 'b_3', 'c_3'],
|
||||
['a_4', 'b_4', 'c_4'],
|
||||
['Footer A', 'Footer B', 'Footer C'],
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -157,6 +162,7 @@ describe('MatTable', () => {
|
||||
expectTableToMatchContent(tableElement, [
|
||||
['Column A', 'Column B', 'Column C'],
|
||||
['a_1', 'b_1', 'c_1'],
|
||||
['Footer A', 'Footer B', 'Footer C'],
|
||||
]);
|
||||
|
||||
flushMicrotasks(); // Resolve promise that updates paginator's length
|
||||
@@ -170,6 +176,7 @@ describe('MatTable', () => {
|
||||
expectTableToMatchContent(tableElement, [
|
||||
['Column A', 'Column B', 'Column C'],
|
||||
['a_2', 'b_2', 'c_2'],
|
||||
['Footer A', 'Footer B', 'Footer C'],
|
||||
]);
|
||||
|
||||
// Change filter to empty string, should match all rows
|
||||
@@ -184,6 +191,7 @@ describe('MatTable', () => {
|
||||
['a_1', 'b_1', 'c_1'],
|
||||
['a_2', 'b_2', 'c_2'],
|
||||
['a_3', 'b_3', 'c_3'],
|
||||
['Footer A', 'Footer B', 'Footer C'],
|
||||
]);
|
||||
|
||||
// Change filter function and filter, should match to rows with zebra.
|
||||
@@ -203,6 +211,7 @@ describe('MatTable', () => {
|
||||
expectTableToMatchContent(tableElement, [
|
||||
['Column A', 'Column B', 'Column C'],
|
||||
['a_2', 'b_2', 'c_2'],
|
||||
['Footer A', 'Footer B', 'Footer C'],
|
||||
]);
|
||||
}));
|
||||
|
||||
@@ -215,6 +224,7 @@ describe('MatTable', () => {
|
||||
['a_1', 'b_1', 'c_1'],
|
||||
['a_2', 'b_2', 'c_2'],
|
||||
['a_3', 'b_3', 'c_3'],
|
||||
['Footer A', 'Footer B', 'Footer C'],
|
||||
]);
|
||||
|
||||
// Activate column A sort again (reverse direction)
|
||||
@@ -225,6 +235,7 @@ describe('MatTable', () => {
|
||||
['a_3', 'b_3', 'c_3'],
|
||||
['a_2', 'b_2', 'c_2'],
|
||||
['a_1', 'b_1', 'c_1'],
|
||||
['Footer A', 'Footer B', 'Footer C'],
|
||||
]);
|
||||
|
||||
// Change sort function to customize how it sorts - first column 1, then 3, then 2
|
||||
@@ -243,6 +254,7 @@ describe('MatTable', () => {
|
||||
['a_1', 'b_1', 'c_1'],
|
||||
['a_3', 'b_3', 'c_3'],
|
||||
['a_2', 'b_2', 'c_2'],
|
||||
['Footer A', 'Footer B', 'Footer C'],
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -258,6 +270,7 @@ describe('MatTable', () => {
|
||||
['', 'b_1', 'c_1'],
|
||||
['a_2', 'b_2', 'c_2'],
|
||||
['a_3', 'b_3', 'c_3'],
|
||||
['Footer A', 'Footer B', 'Footer C'],
|
||||
]);
|
||||
|
||||
// Expect that empty string row comes before the other values
|
||||
@@ -268,6 +281,7 @@ describe('MatTable', () => {
|
||||
['a_3', 'b_3', 'c_3'],
|
||||
['a_2', 'b_2', 'c_2'],
|
||||
['', 'b_1', 'c_1'],
|
||||
['Footer A', 'Footer B', 'Footer C'],
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -283,6 +297,7 @@ describe('MatTable', () => {
|
||||
['', 'b_1', 'c_1'],
|
||||
['a_2', 'b_2', 'c_2'],
|
||||
['a_3', 'b_3', 'c_3'],
|
||||
['Footer A', 'Footer B', 'Footer C'],
|
||||
]);
|
||||
|
||||
|
||||
@@ -294,6 +309,7 @@ describe('MatTable', () => {
|
||||
['a_3', 'b_3', 'c_3'],
|
||||
['a_2', 'b_2', 'c_2'],
|
||||
['', 'b_1', 'c_1'],
|
||||
['Footer A', 'Footer B', 'Footer C'],
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -311,6 +327,7 @@ describe('MatTable', () => {
|
||||
['-1', 'b_3', 'c_3'],
|
||||
['0', 'b_2', 'c_2'],
|
||||
['1', 'b_1', 'c_1'],
|
||||
['Footer A', 'Footer B', 'Footer C'],
|
||||
]);
|
||||
|
||||
|
||||
@@ -323,6 +340,7 @@ describe('MatTable', () => {
|
||||
['1', 'b_1', 'c_1'],
|
||||
['0', 'b_2', 'c_2'],
|
||||
['-1', 'b_3', 'c_3'],
|
||||
['Footer A', 'Footer B', 'Footer C'],
|
||||
]);
|
||||
});
|
||||
|
||||
@@ -340,6 +358,7 @@ describe('MatTable', () => {
|
||||
['a_3', 'b_3', 'c_3'],
|
||||
['a_4', 'b_4', 'c_4'],
|
||||
['a_5', 'b_5', 'c_5'],
|
||||
['Footer A', 'Footer B', 'Footer C'],
|
||||
]);
|
||||
|
||||
// Navigate to the next page
|
||||
@@ -352,6 +371,7 @@ describe('MatTable', () => {
|
||||
['a_8', 'b_8', 'c_8'],
|
||||
['a_9', 'b_9', 'c_9'],
|
||||
['a_10', 'b_10', 'c_10'],
|
||||
['Footer A', 'Footer B', 'Footer C'],
|
||||
]);
|
||||
}));
|
||||
});
|
||||
@@ -399,16 +419,19 @@ class FakeDataSource extends DataSource<TestData> {
|
||||
<ng-container matColumnDef="column_a">
|
||||
<mat-header-cell *matHeaderCellDef> Column A</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row"> {{row.a}}</mat-cell>
|
||||
<mat-footer-cell *matFooterCellDef> Footer A</mat-footer-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="column_b">
|
||||
<mat-header-cell *matHeaderCellDef> Column B</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row"> {{row.b}}</mat-cell>
|
||||
<mat-footer-cell *matFooterCellDef> Footer B</mat-footer-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="column_c">
|
||||
<mat-header-cell *matHeaderCellDef> Column C</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row"> {{row.c}}</mat-cell>
|
||||
<mat-footer-cell *matFooterCellDef> Footer C</mat-footer-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="special_column">
|
||||
@@ -418,6 +441,7 @@ class FakeDataSource extends DataSource<TestData> {
|
||||
<mat-header-row *matHeaderRowDef="columnsToRender"></mat-header-row>
|
||||
<mat-row *matRowDef="let row; columns: columnsToRender"></mat-row>
|
||||
<mat-row *matRowDef="let row; columns: ['special_column']; when: isFourthRow"></mat-row>
|
||||
<mat-footer-row *matFooterRowDef="columnsToRender"></mat-footer-row>
|
||||
</mat-table>
|
||||
`
|
||||
})
|
||||
@@ -466,6 +490,7 @@ class NativeHtmlTableApp {
|
||||
<ng-container matColumnDef="column_a">
|
||||
<mat-header-cell *matHeaderCellDef> Column A</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row"> {{row.a}}</mat-cell>
|
||||
<mat-footer-cell *matFooterCellDef> Footer A</mat-footer-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="special_column">
|
||||
@@ -475,6 +500,7 @@ class NativeHtmlTableApp {
|
||||
<mat-header-row *matHeaderRowDef="['column_a']"></mat-header-row>
|
||||
<mat-row *matRowDef="let row; columns: ['column_a']"></mat-row>
|
||||
<mat-row *matRowDef="let row; columns: ['special_column']; when: isFourthRow"></mat-row>
|
||||
<mat-footer-row *matFooterRowDef="['column_a']"></mat-footer-row>
|
||||
</mat-table>
|
||||
`
|
||||
})
|
||||
@@ -492,20 +518,24 @@ class MatTableWithWhenRowApp {
|
||||
<ng-container matColumnDef="column_a">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header="a"> Column A</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row"> {{row.a}}</mat-cell>
|
||||
<mat-footer-cell *matFooterCellDef> Footer A</mat-footer-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="column_b">
|
||||
<mat-header-cell *matHeaderCellDef> Column B</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row"> {{row.b}}</mat-cell>
|
||||
<mat-footer-cell *matFooterCellDef> Footer B</mat-footer-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="column_c">
|
||||
<mat-header-cell *matHeaderCellDef> Column C</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row"> {{row.c}}</mat-cell>
|
||||
<mat-footer-cell *matFooterCellDef> Footer C</mat-footer-cell>
|
||||
</ng-container>
|
||||
|
||||
<mat-header-row *matHeaderRowDef="columnsToRender"></mat-header-row>
|
||||
<mat-row *matRowDef="let row; columns: columnsToRender"></mat-row>
|
||||
<mat-footer-row *matFooterRowDef="columnsToRender"></mat-footer-row>
|
||||
</mat-table>
|
||||
|
||||
<mat-paginator [pageSize]="5"></mat-paginator>
|
||||
@@ -641,7 +671,6 @@ class MatTableWithPaginatorApp {
|
||||
}
|
||||
}
|
||||
|
||||
// Utilities copied from CDKTable's spec
|
||||
function getElements(element: Element, query: string): Element[] {
|
||||
return [].slice.call(element.querySelectorAll(query));
|
||||
}
|
||||
@@ -650,6 +679,10 @@ function getHeaderRow(tableElement: Element): Element {
|
||||
return tableElement.querySelector('.mat-header-row')!;
|
||||
}
|
||||
|
||||
function getFooterRow(tableElement: Element): Element {
|
||||
return tableElement.querySelector('.mat-footer-row')!;
|
||||
}
|
||||
|
||||
function getRows(tableElement: Element): Element[] {
|
||||
return getElements(tableElement, '.mat-row');
|
||||
}
|
||||
@@ -661,35 +694,46 @@ function getHeaderCells(tableElement: Element): Element[] {
|
||||
return getElements(getHeaderRow(tableElement), '.mat-header-cell');
|
||||
}
|
||||
|
||||
function expectTableToMatchContent(tableElement: Element, expectedTableContent: any[]) {
|
||||
function getFooterCells(tableElement: Element): Element[] {
|
||||
return getElements(getFooterRow(tableElement), '.mat-footer-cell');
|
||||
}
|
||||
|
||||
function getActualTableContent(tableElement: Element): string[][] {
|
||||
let actualTableContent: Element[][] = [];
|
||||
if (getHeaderRow(tableElement)) {
|
||||
actualTableContent.push(getHeaderCells(tableElement));
|
||||
}
|
||||
|
||||
// Check data row cells
|
||||
const rows = getRows(tableElement).map(row => getCells(row));
|
||||
actualTableContent = actualTableContent.concat(rows);
|
||||
|
||||
if (getFooterRow(tableElement)) {
|
||||
actualTableContent.push(getFooterCells(tableElement));
|
||||
}
|
||||
|
||||
// Convert the nodes into their text content;
|
||||
return actualTableContent.map(row => row.map(cell => cell.textContent!.trim()));
|
||||
}
|
||||
|
||||
function expectTableToMatchContent(tableElement: Element, expected: any[]) {
|
||||
const missedExpectations: string[] = [];
|
||||
function checkCellContent(cell: Element, expectedTextContent: string) {
|
||||
const actualTextContent = cell.textContent!.trim();
|
||||
if (actualTextContent !== expectedTextContent) {
|
||||
missedExpectations.push(
|
||||
`Expected cell contents to be ${expectedTextContent} but was ${actualTextContent}`);
|
||||
function checkCellContent(actualCell: string, expectedCell: string) {
|
||||
if (actualCell !== expectedCell) {
|
||||
missedExpectations.push(`Expected cell contents to be ${actualCell} but was ${expectedCell}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Check header cells
|
||||
const expectedHeaderContent = expectedTableContent.shift();
|
||||
getHeaderCells(tableElement).forEach((cell, index) => {
|
||||
const expected = expectedHeaderContent ?
|
||||
expectedHeaderContent[index] :
|
||||
null;
|
||||
checkCellContent(cell, expected);
|
||||
});
|
||||
const actual = getActualTableContent(tableElement);
|
||||
|
||||
// Check data row cells
|
||||
const rows = getRows(tableElement);
|
||||
expect(rows.length).toBe(expectedTableContent.length,
|
||||
`Found ${rows.length} rows but expected ${expectedTableContent.length}`);
|
||||
rows.forEach((row, rowIndex) => {
|
||||
getCells(row).forEach((cell, cellIndex) => {
|
||||
const expected = expectedTableContent.length ?
|
||||
expectedTableContent[rowIndex][cellIndex] :
|
||||
null;
|
||||
checkCellContent(cell, expected);
|
||||
if (actual.length !== expected.length) {
|
||||
missedExpectations.push(`Expected ${expected.length} total rows but got ${actual.length}`);
|
||||
fail(missedExpectations.join('\n'));
|
||||
}
|
||||
actual.forEach((row, rowIndex) => {
|
||||
row.forEach((actualCell, cellIndex) => {
|
||||
const expectedCell = expected[rowIndex] ? expected[rowIndex][cellIndex] : null;
|
||||
checkCellContent(actualCell, expectedCell);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
.example-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 500px;
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
.mat-table {
|
||||
overflow: auto;
|
||||
max-height: 500px;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<div class="example-container mat-elevation-z8">
|
||||
<mat-table [dataSource]="transactions">
|
||||
|
||||
<!-- Item Column -->
|
||||
<ng-container matColumnDef="item">
|
||||
<mat-header-cell *matHeaderCellDef> Item </mat-header-cell>
|
||||
<mat-cell *matCellDef="let transaction"> {{transaction.item}} </mat-cell>
|
||||
<mat-footer-cell *matFooterCellDef></mat-footer-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Cost Column -->
|
||||
<ng-container matColumnDef="cost">
|
||||
<mat-header-cell *matHeaderCellDef> Cost </mat-header-cell>
|
||||
<mat-cell *matCellDef="let transaction"> {{transaction.cost | currency}} </mat-cell>
|
||||
<mat-footer-cell *matFooterCellDef> getTotalCost() </mat-footer-cell>
|
||||
</ng-container>
|
||||
|
||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||
<mat-row *matRowDef="let row; columns: displayedColumns;"></mat-row>
|
||||
<mat-footer-row *matFooterRowDef="displayedColumns"></mat-footer-row>
|
||||
</mat-table>
|
||||
</div>
|
||||
@@ -0,0 +1,31 @@
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
export interface Transaction {
|
||||
item: string;
|
||||
cost: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @title Footer row table
|
||||
*/
|
||||
@Component({
|
||||
selector: 'table-footer-row-example',
|
||||
styleUrls: ['table-footer-row-example.css'],
|
||||
templateUrl: 'table-footer-row-example.html',
|
||||
})
|
||||
export class TableFooterRowExample {
|
||||
displayedColumns = ['item', 'cost'];
|
||||
transactions: Transaction[] = [
|
||||
{item: 'Beach ball', cost: 4},
|
||||
{item: 'Towel', cost: 5},
|
||||
{item: 'Frisbee', cost: 2},
|
||||
{item: 'Sunscreen', cost: 4},
|
||||
{item: 'Cooler', cost: 25},
|
||||
{item: 'Swim suit', cost: 15},
|
||||
];
|
||||
|
||||
/** Gets the total cost of all transactions. */
|
||||
getTotalCost() {
|
||||
return this.transactions.map(t => t.cost).reduce((acc, value) => acc + value, 0);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user