mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
feat(table): add row when predicate (#6795)
This commit is contained in:
+12
-3
@@ -75,13 +75,22 @@ export class CdkHeaderRowDef extends BaseRowDef {
|
||||
|
||||
/**
|
||||
* Data row definition for the CDK table.
|
||||
* Captures the header row's template and other row properties such as the columns to display.
|
||||
* Captures the header row's template and other row properties such as the columns to display and
|
||||
* a when predicate that describes when this row should be used.
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[cdkRowDef]',
|
||||
inputs: ['columns: cdkRowDefColumns'],
|
||||
inputs: ['columns: cdkRowDefColumns', 'when: cdkRowDefWhen'],
|
||||
})
|
||||
export class CdkRowDef extends BaseRowDef {
|
||||
export class CdkRowDef<T> extends BaseRowDef {
|
||||
/**
|
||||
* Function that should return true if this row template should be used for the provided row data
|
||||
* and index. If left undefined, this row will be considered the default row template to use when
|
||||
* no other when functions return true for the data.
|
||||
* For every row, there must be at least one when function that passes or an undefined to default.
|
||||
*/
|
||||
when: (rowData: T, index: number) => boolean;
|
||||
|
||||
// TODO(andrewseguin): Add an input for providing a switch function to determine
|
||||
// if this template should be used.
|
||||
constructor(template: TemplateRef<any>, _differs: IterableDiffers) {
|
||||
|
||||
@@ -22,3 +22,19 @@ export function getTableUnknownColumnError(id: string) {
|
||||
export function getTableDuplicateColumnNameError(name: string) {
|
||||
return Error(`cdk-table: Duplicate column definition name provided: "${name}".`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an error to be thrown when there are multiple rows that are missing a when function.
|
||||
* @docs-private
|
||||
*/
|
||||
export function getTableMultipleDefaultRowDefsError() {
|
||||
return Error(`cdk-table: There can only be one default row without a when predicate function.`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an error to be thrown when there are no matching row defs for a particular set of data.
|
||||
* @docs-private
|
||||
*/
|
||||
export function getTableMissingMatchingRowDefError() {
|
||||
return Error(`cdk-table: Could not find a matching row definition for the provided row data.`);
|
||||
}
|
||||
|
||||
+170
-1
@@ -7,7 +7,12 @@ import {Observable} from 'rxjs/Observable';
|
||||
import {combineLatest} from 'rxjs/observable/combineLatest';
|
||||
import {CdkTableModule} from './index';
|
||||
import {map} from 'rxjs/operator/map';
|
||||
import {getTableDuplicateColumnNameError, getTableUnknownColumnError} from './table-errors';
|
||||
import {
|
||||
getTableDuplicateColumnNameError,
|
||||
getTableMissingMatchingRowDefError,
|
||||
getTableMultipleDefaultRowDefsError,
|
||||
getTableUnknownColumnError
|
||||
} from './table-errors';
|
||||
|
||||
describe('CdkTable', () => {
|
||||
let fixture: ComponentFixture<SimpleCdkTableApp>;
|
||||
@@ -31,6 +36,9 @@ describe('CdkTable', () => {
|
||||
MissingColumnDefCdkTableApp,
|
||||
CrazyColumnNameCdkTableApp,
|
||||
UndefinedColumnsCdkTableApp,
|
||||
WhenRowCdkTableApp,
|
||||
WhenRowWithoutDefaultCdkTableApp,
|
||||
WhenRowMultipleDefaultsCdkTableApp
|
||||
],
|
||||
}).compileComponents();
|
||||
}));
|
||||
@@ -202,6 +210,34 @@ describe('CdkTable', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('using when predicate', () => {
|
||||
it('should be able to display different row templates based on the row data', () => {
|
||||
let whenFixture = TestBed.createComponent(WhenRowCdkTableApp);
|
||||
whenFixture.detectChanges();
|
||||
|
||||
let data = whenFixture.componentInstance.dataSource.data;
|
||||
expectTableToMatchContent(whenFixture.nativeElement.querySelector('cdk-table'), [
|
||||
['Column A', 'Column B', 'Column C'],
|
||||
[data[0].a, data[0].b, data[0].c],
|
||||
['index_1_special_row'],
|
||||
['c3_special_row'],
|
||||
[data[3].a, data[3].b, data[3].c],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should error if there is row data that does not have a matching row template', () => {
|
||||
let whenFixture = TestBed.createComponent(WhenRowWithoutDefaultCdkTableApp);
|
||||
expect(() => whenFixture.detectChanges())
|
||||
.toThrowError(getTableMissingMatchingRowDefError().message);
|
||||
});
|
||||
|
||||
it('should error if there are multiple rows that do not have a when function', () => {
|
||||
let whenFixture = TestBed.createComponent(WhenRowMultipleDefaultsCdkTableApp);
|
||||
expect(() => whenFixture.detectChanges())
|
||||
.toThrowError(getTableMultipleDefaultRowDefsError().message);
|
||||
});
|
||||
});
|
||||
|
||||
it('should use differ to add/remove/move rows', () => {
|
||||
// Each row receives an attribute 'initialIndex' the element's original place
|
||||
getRows(tableElement).forEach((row: Element, index: number) => {
|
||||
@@ -615,6 +651,139 @@ class SimpleCdkTableApp {
|
||||
@ViewChild(CdkTable) table: CdkTable<TestData>;
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<cdk-table [dataSource]="dataSource">
|
||||
<ng-container cdkColumnDef="column_a">
|
||||
<cdk-header-cell *cdkHeaderCellDef> Column A</cdk-header-cell>
|
||||
<cdk-cell *cdkCellDef="let row"> {{row.a}}</cdk-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container cdkColumnDef="column_b">
|
||||
<cdk-header-cell *cdkHeaderCellDef> Column B</cdk-header-cell>
|
||||
<cdk-cell *cdkCellDef="let row"> {{row.b}}</cdk-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container cdkColumnDef="column_c">
|
||||
<cdk-header-cell *cdkHeaderCellDef> Column C</cdk-header-cell>
|
||||
<cdk-cell *cdkCellDef="let row"> {{row.c}}</cdk-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container cdkColumnDef="index1Column">
|
||||
<cdk-header-cell *cdkHeaderCellDef> Column C</cdk-header-cell>
|
||||
<cdk-cell *cdkCellDef="let row"> index_1_special_row </cdk-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container cdkColumnDef="c3Column">
|
||||
<cdk-header-cell *cdkHeaderCellDef> Column C</cdk-header-cell>
|
||||
<cdk-cell *cdkCellDef="let row"> c3_special_row </cdk-cell>
|
||||
</ng-container>
|
||||
|
||||
<cdk-header-row *cdkHeaderRowDef="columnsToRender"></cdk-header-row>
|
||||
<cdk-row *cdkRowDef="let row; columns: columnsToRender"></cdk-row>
|
||||
<cdk-row *cdkRowDef="let row; columns: ['index1Column']; when: isIndex1"></cdk-row>
|
||||
<cdk-row *cdkRowDef="let row; columns: ['c3Column']; when: hasC3"></cdk-row>
|
||||
</cdk-table>
|
||||
`
|
||||
})
|
||||
class WhenRowCdkTableApp {
|
||||
dataSource: FakeDataSource = new FakeDataSource();
|
||||
columnsToRender = ['column_a', 'column_b', 'column_c'];
|
||||
isIndex1 = (_rowData: TestData, index: number) => index == 1;
|
||||
hasC3 = (rowData: TestData) => rowData.c == 'c_3';
|
||||
|
||||
constructor() { this.dataSource.addData(); }
|
||||
|
||||
@ViewChild(CdkTable) table: CdkTable<TestData>;
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<cdk-table [dataSource]="dataSource">
|
||||
<ng-container cdkColumnDef="column_a">
|
||||
<cdk-header-cell *cdkHeaderCellDef> Column A</cdk-header-cell>
|
||||
<cdk-cell *cdkCellDef="let row"> {{row.a}}</cdk-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container cdkColumnDef="column_b">
|
||||
<cdk-header-cell *cdkHeaderCellDef> Column B</cdk-header-cell>
|
||||
<cdk-cell *cdkCellDef="let row"> {{row.b}}</cdk-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container cdkColumnDef="column_c">
|
||||
<cdk-header-cell *cdkHeaderCellDef> Column C</cdk-header-cell>
|
||||
<cdk-cell *cdkCellDef="let row"> {{row.c}}</cdk-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container cdkColumnDef="index1Column">
|
||||
<cdk-header-cell *cdkHeaderCellDef> Column C</cdk-header-cell>
|
||||
<cdk-cell *cdkCellDef="let row"> index_1_special_row </cdk-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container cdkColumnDef="c3Column">
|
||||
<cdk-header-cell *cdkHeaderCellDef> Column C</cdk-header-cell>
|
||||
<cdk-cell *cdkCellDef="let row"> c3_special_row </cdk-cell>
|
||||
</ng-container>
|
||||
|
||||
<cdk-header-row *cdkHeaderRowDef="columnsToRender"></cdk-header-row>
|
||||
<cdk-row *cdkRowDef="let row; columns: ['index1Column']; when: isIndex1"></cdk-row>
|
||||
<cdk-row *cdkRowDef="let row; columns: ['c3Column']; when: hasC3"></cdk-row>
|
||||
</cdk-table>
|
||||
`
|
||||
})
|
||||
class WhenRowWithoutDefaultCdkTableApp {
|
||||
dataSource: FakeDataSource = new FakeDataSource();
|
||||
columnsToRender = ['column_a', 'column_b', 'column_c'];
|
||||
isIndex1 = (_rowData: TestData, index: number) => index == 1;
|
||||
hasC3 = (rowData: TestData) => rowData.c == 'c_3';
|
||||
|
||||
@ViewChild(CdkTable) table: CdkTable<TestData>;
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<cdk-table [dataSource]="dataSource">
|
||||
<ng-container cdkColumnDef="column_a">
|
||||
<cdk-header-cell *cdkHeaderCellDef> Column A</cdk-header-cell>
|
||||
<cdk-cell *cdkCellDef="let row"> {{row.a}}</cdk-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container cdkColumnDef="column_b">
|
||||
<cdk-header-cell *cdkHeaderCellDef> Column B</cdk-header-cell>
|
||||
<cdk-cell *cdkCellDef="let row"> {{row.b}}</cdk-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container cdkColumnDef="column_c">
|
||||
<cdk-header-cell *cdkHeaderCellDef> Column C</cdk-header-cell>
|
||||
<cdk-cell *cdkCellDef="let row"> {{row.c}}</cdk-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container cdkColumnDef="index1Column">
|
||||
<cdk-header-cell *cdkHeaderCellDef> Column C</cdk-header-cell>
|
||||
<cdk-cell *cdkCellDef="let row"> index_1_special_row </cdk-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container cdkColumnDef="c3Column">
|
||||
<cdk-header-cell *cdkHeaderCellDef> Column C</cdk-header-cell>
|
||||
<cdk-cell *cdkCellDef="let row"> c3_special_row </cdk-cell>
|
||||
</ng-container>
|
||||
|
||||
<cdk-header-row *cdkHeaderRowDef="columnsToRender"></cdk-header-row>
|
||||
<cdk-row *cdkRowDef="let row; columns: columnsToRender"></cdk-row>
|
||||
<cdk-row *cdkRowDef="let row; columns: ['index1Column']"></cdk-row>
|
||||
<cdk-row *cdkRowDef="let row; columns: ['c3Column']; when: hasC3"></cdk-row>
|
||||
</cdk-table>
|
||||
`
|
||||
})
|
||||
class WhenRowMultipleDefaultsCdkTableApp {
|
||||
dataSource: FakeDataSource = new FakeDataSource();
|
||||
columnsToRender = ['column_a', 'column_b', 'column_c'];
|
||||
isIndex1 = (_rowData: TestData, index: number) => index == 1;
|
||||
hasC3 = (rowData: TestData) => rowData.c == 'c_3';
|
||||
|
||||
@ViewChild(CdkTable) table: CdkTable<TestData>;
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<cdk-table [dataSource]="dataSource">
|
||||
|
||||
+50
-27
@@ -36,7 +36,11 @@ import {BehaviorSubject} from 'rxjs/BehaviorSubject';
|
||||
import {Subscription} from 'rxjs/Subscription';
|
||||
import {Subject} from 'rxjs/Subject';
|
||||
import {CdkCellDef, CdkColumnDef, CdkHeaderCellDef} from './cell';
|
||||
import {getTableDuplicateColumnNameError, getTableUnknownColumnError} from './table-errors';
|
||||
import {
|
||||
getTableDuplicateColumnNameError, getTableMissingMatchingRowDefError,
|
||||
getTableMultipleDefaultRowDefsError,
|
||||
getTableUnknownColumnError
|
||||
} from './table-errors';
|
||||
|
||||
/**
|
||||
* Provides a handle for the table to grab the view container's ng-container to insert data rows.
|
||||
@@ -90,11 +94,14 @@ export class CdkTable<T> implements CollectionViewer {
|
||||
private _renderChangeSubscription: Subscription | null;
|
||||
|
||||
/** Map of all the user's defined columns (header and data cell template) identified by name. */
|
||||
private _columnDefinitionsByName = new Map<string, CdkColumnDef>();
|
||||
private _columnDefsByName = new Map<string, CdkColumnDef>();
|
||||
|
||||
/** Differ used to find the changes in the data provided by the data source. */
|
||||
private _dataDiffer: IterableDiffer<T>;
|
||||
|
||||
/** Stores the row definition that does not have a when predicate. */
|
||||
private _defaultRowDef: CdkRowDef<T> | null;
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -143,13 +150,13 @@ export class CdkTable<T> implements CollectionViewer {
|
||||
* The column definitions provided by the user that contain what the header and cells should
|
||||
* render for each column.
|
||||
*/
|
||||
@ContentChildren(CdkColumnDef) _columnDefinitions: QueryList<CdkColumnDef>;
|
||||
@ContentChildren(CdkColumnDef) _columnDefs: QueryList<CdkColumnDef>;
|
||||
|
||||
/** Template used as the header container. */
|
||||
@ContentChild(CdkHeaderRowDef) _headerDefinition: CdkHeaderRowDef;
|
||||
/** Template definition used as the header container. */
|
||||
@ContentChild(CdkHeaderRowDef) _headerDef: CdkHeaderRowDef;
|
||||
|
||||
/** Set of templates that used as the data row containers. */
|
||||
@ContentChildren(CdkRowDef) _rowDefinitions: QueryList<CdkRowDef>;
|
||||
/** Set of template definitions that used as the data row containers. */
|
||||
@ContentChildren(CdkRowDef) _rowDefs: QueryList<CdkRowDef<T>>;
|
||||
|
||||
constructor(private readonly _differs: IterableDiffers,
|
||||
private readonly _changeDetectorRef: ChangeDetectorRef,
|
||||
@@ -167,13 +174,18 @@ export class CdkTable<T> implements CollectionViewer {
|
||||
}
|
||||
|
||||
ngAfterContentInit() {
|
||||
this._cacheColumnDefinitionsByName();
|
||||
this._columnDefinitions.changes.subscribe(() => this._cacheColumnDefinitionsByName());
|
||||
this._cacheColumnDefsByName();
|
||||
this._columnDefs.changes.subscribe(() => this._cacheColumnDefsByName());
|
||||
this._renderHeaderRow();
|
||||
}
|
||||
|
||||
ngAfterContentChecked() {
|
||||
this._renderUpdatedColumns();
|
||||
|
||||
const defaultRowDefs = this._rowDefs.filter(def => !def.when);
|
||||
if (defaultRowDefs.length > 1) { throw getTableMultipleDefaultRowDefsError(); }
|
||||
this._defaultRowDef = defaultRowDefs[0];
|
||||
|
||||
if (this.dataSource && !this._renderChangeSubscription) {
|
||||
this._observeRenderChanges();
|
||||
}
|
||||
@@ -190,15 +202,14 @@ export class CdkTable<T> implements CollectionViewer {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/** Update the map containing the content's column definitions. */
|
||||
private _cacheColumnDefinitionsByName() {
|
||||
this._columnDefinitionsByName.clear();
|
||||
this._columnDefinitions.forEach(columnDef => {
|
||||
if (this._columnDefinitionsByName.has(columnDef.name)) {
|
||||
private _cacheColumnDefsByName() {
|
||||
this._columnDefsByName.clear();
|
||||
this._columnDefs.forEach(columnDef => {
|
||||
if (this._columnDefsByName.has(columnDef.name)) {
|
||||
throw getTableDuplicateColumnNameError(columnDef.name);
|
||||
}
|
||||
this._columnDefinitionsByName.set(columnDef.name, columnDef);
|
||||
this._columnDefsByName.set(columnDef.name, columnDef);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -208,8 +219,8 @@ export class CdkTable<T> implements CollectionViewer {
|
||||
*/
|
||||
private _renderUpdatedColumns() {
|
||||
// Re-render the rows when the row definition columns change.
|
||||
this._rowDefinitions.forEach(rowDefinition => {
|
||||
if (!!rowDefinition.getColumnsDiff()) {
|
||||
this._rowDefs.forEach(def => {
|
||||
if (!!def.getColumnsDiff()) {
|
||||
// Reset the data to an empty array so that renderRowChanges will re-render all new rows.
|
||||
this._dataDiffer.diff([]);
|
||||
|
||||
@@ -219,7 +230,7 @@ export class CdkTable<T> implements CollectionViewer {
|
||||
});
|
||||
|
||||
// Re-render the header row if there is a difference in its columns.
|
||||
if (this._headerDefinition.getColumnsDiff()) {
|
||||
if (this._headerDef.getColumnsDiff()) {
|
||||
this._headerRowPlaceholder.viewContainer.clear();
|
||||
this._renderHeaderRow();
|
||||
}
|
||||
@@ -264,14 +275,14 @@ export class CdkTable<T> implements CollectionViewer {
|
||||
* Create the embedded view for the header template and place it in the header row view container.
|
||||
*/
|
||||
private _renderHeaderRow() {
|
||||
const cells = this._getHeaderCellTemplatesForRow(this._headerDefinition);
|
||||
const cells = this._getHeaderCellTemplatesForRow(this._headerDef);
|
||||
if (!cells.length) { return; }
|
||||
|
||||
// TODO(andrewseguin): add some code to enforce that exactly
|
||||
// one CdkCellOutlet was instantiated as a result
|
||||
// of `createEmbeddedView`.
|
||||
this._headerRowPlaceholder.viewContainer
|
||||
.createEmbeddedView(this._headerDefinition.template, {cells});
|
||||
.createEmbeddedView(this._headerDef.template, {cells});
|
||||
|
||||
cells.forEach(cell => {
|
||||
CdkCellOutlet.mostRecentCellOutlet._viewContainer.createEmbeddedView(cell.template, {});
|
||||
@@ -301,15 +312,27 @@ export class CdkTable<T> implements CollectionViewer {
|
||||
this._updateRowContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the matching row definition that should be used for this row data. If there is only
|
||||
* one row definition, it is returned. Otherwise, find the row definition that has a when
|
||||
* predicate that returns true with the data. If none return true, return the default row
|
||||
* definition.
|
||||
*/
|
||||
_getRowDef(data: T, i: number): CdkRowDef<T> {
|
||||
if (this._rowDefs.length == 1) { return this._rowDefs.first; }
|
||||
|
||||
let rowDef = this._rowDefs.find(def => def.when && def.when(data, i)) || this._defaultRowDef;
|
||||
if (!rowDef) { throw getTableMissingMatchingRowDefError(); }
|
||||
|
||||
return rowDef;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the embedded view for the data row template and place it in the correct index location
|
||||
* within the data row view container.
|
||||
*/
|
||||
private _insertRow(rowData: T, index: number) {
|
||||
// TODO(andrewseguin): Add when predicates to the row definitions
|
||||
// to find the right template to used based on
|
||||
// the data rather than choosing the first row definition.
|
||||
const row = this._rowDefinitions.first;
|
||||
const row = this._getRowDef(rowData, index);
|
||||
|
||||
// Row context that will be provided to both the created embedded row view and its cells.
|
||||
const context: CdkCellOutletRowContext<T> = {$implicit: rowData};
|
||||
@@ -353,7 +376,7 @@ export class CdkTable<T> implements CollectionViewer {
|
||||
private _getHeaderCellTemplatesForRow(headerDef: CdkHeaderRowDef): CdkHeaderCellDef[] {
|
||||
if (!headerDef.columns) { return []; }
|
||||
return headerDef.columns.map(columnId => {
|
||||
const column = this._columnDefinitionsByName.get(columnId);
|
||||
const column = this._columnDefsByName.get(columnId);
|
||||
|
||||
if (!column) {
|
||||
throw getTableUnknownColumnError(columnId);
|
||||
@@ -367,10 +390,10 @@ export class CdkTable<T> implements CollectionViewer {
|
||||
* Returns the cell template definitions to insert in the provided row
|
||||
* as defined by its list of columns to display.
|
||||
*/
|
||||
private _getCellTemplatesForRow(rowDef: CdkRowDef): CdkCellDef[] {
|
||||
private _getCellTemplatesForRow(rowDef: CdkRowDef<T>): CdkCellDef[] {
|
||||
if (!rowDef.columns) { return []; }
|
||||
return rowDef.columns.map(columnId => {
|
||||
const column = this._columnDefinitionsByName.get(columnId);
|
||||
const column = this._columnDefsByName.get(columnId);
|
||||
|
||||
if (!column) {
|
||||
throw getTableUnknownColumnError(columnId);
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import {DataSource} from '@angular/cdk/collections';
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
import {UserData} from './people-database';
|
||||
import 'rxjs/add/observable/merge';
|
||||
import 'rxjs/add/operator/map';
|
||||
import {PersonDataSource} from './person-data-source';
|
||||
|
||||
export interface DetailRow {
|
||||
detailRow: boolean;
|
||||
data: UserData;
|
||||
}
|
||||
|
||||
export class PersonDetailDataSource extends DataSource<any> {
|
||||
constructor(private _personDataSource: PersonDataSource) {
|
||||
super();
|
||||
}
|
||||
|
||||
connect(): Observable<(UserData|DetailRow)[]> {
|
||||
return this._personDataSource.connect().map(data => {
|
||||
const rows: (UserData|DetailRow)[] = [];
|
||||
|
||||
// Interweave a detail data object for each row data object that will be used for displaying
|
||||
// row details. Contains the row data.
|
||||
data.forEach(person => rows.push(person, {detailRow: true, data: person}));
|
||||
|
||||
return rows;
|
||||
});
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
// No-op
|
||||
}
|
||||
}
|
||||
@@ -167,6 +167,46 @@
|
||||
|
||||
<mat-paginator #paginator
|
||||
[length]="_peopleDatabase.data.length"
|
||||
[pageSize]="10"
|
||||
[pageSizeOptions]="[5, 10, 25, 100]">
|
||||
</mat-paginator>
|
||||
</div>
|
||||
|
||||
<mat-card class="demo-table-card">
|
||||
<h3> MatTable Using 'When' Rows for Interactive Details</h3>
|
||||
|
||||
<mat-table [dataSource]="dataSourceWithDetails">
|
||||
<!-- Column Definition: ID -->
|
||||
<ng-container matColumnDef="userId">
|
||||
<mat-header-cell *matHeaderCellDef> ID </mat-header-cell>
|
||||
<mat-cell *matCellDef="let row"> {{row.id}} </mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Column Definition: Name -->
|
||||
<ng-container matColumnDef="userName">
|
||||
<mat-header-cell *matHeaderCellDef> Name </mat-header-cell>
|
||||
<mat-cell *matCellDef="let row"> {{row.name}} </mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<!-- Detail Column -->
|
||||
<ng-container matColumnDef="details">
|
||||
<mat-cell *matCellDef="let row">
|
||||
<ng-container *ngIf="wasExpanded.has(row.data)">
|
||||
{{row.data.name.split(' ')[0]}}'s favorite color is {{row.data.color}} and has a progress
|
||||
of {{row.data.progress}}%.
|
||||
</ng-container>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<mat-header-row *matHeaderRowDef="['userId', 'userName']"></mat-header-row>
|
||||
<mat-row *matRowDef="let row; columns: ['userId', 'userName'];"
|
||||
matRipple
|
||||
class="user-row"
|
||||
[style.borderBottomColor]="expandedPerson == row ? 'transparent' : ''"
|
||||
(click)="expandedPerson = row; wasExpanded.add(row)"></mat-row>
|
||||
<mat-row *matRowDef="let row; columns: ['details']; when: isDetailRow"
|
||||
[@detailExpand]="row.data == expandedPerson ? 'expanded' : 'collapsed'"
|
||||
style="overflow: hidden">
|
||||
</mat-row>
|
||||
</mat-table>
|
||||
</mat-card>
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
.demo-table-card {
|
||||
margin: 24px 0;
|
||||
max-height: 200px;
|
||||
max-height: 450px;
|
||||
overflow: auto;
|
||||
|
||||
h3 {
|
||||
@@ -34,6 +34,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
.user-row {
|
||||
cursor: pointer;
|
||||
position: relative; // For mdRipple
|
||||
|
||||
&:hover {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
&:active {
|
||||
background: #eaeaea;
|
||||
}
|
||||
}
|
||||
|
||||
/** Styles so that the CDK Table columns have width and font size. */
|
||||
.cdk-table {
|
||||
font-size: 12px;
|
||||
@@ -75,7 +87,6 @@
|
||||
/* Column max widths */
|
||||
.cdk-column-userId {
|
||||
max-width: 32px;
|
||||
text-align: right;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ import {Component, ViewChild} from '@angular/core';
|
||||
import {PeopleDatabase, UserData} from './people-database';
|
||||
import {PersonDataSource} from './person-data-source';
|
||||
import {MatPaginator, MatSort} from '@angular/material';
|
||||
import {DetailRow, PersonDetailDataSource} from './person-detail-data-source';
|
||||
import {animate, state, style, transition, trigger} from '@angular/animations';
|
||||
|
||||
export type UserProperties = 'userId' | 'userName' | 'progress' | 'color' | undefined;
|
||||
|
||||
@@ -14,21 +16,34 @@ const properties = ['id', 'name', 'progress', 'color'];
|
||||
selector: 'table-demo',
|
||||
templateUrl: 'table-demo.html',
|
||||
styleUrls: ['table-demo.css'],
|
||||
animations: [
|
||||
trigger('detailExpand', [
|
||||
state('collapsed', style({height: '0px', minHeight: '0', visibility: 'hidden'})),
|
||||
state('expanded', style({height: '*', visibility: 'visible'})),
|
||||
transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')),
|
||||
]),
|
||||
],
|
||||
})
|
||||
export class TableDemo {
|
||||
dataSource: PersonDataSource | null;
|
||||
dataSourceWithDetails: PersonDetailDataSource | null;
|
||||
displayedColumns: UserProperties[] = [];
|
||||
trackByStrategy: TrackByStrategy = 'reference';
|
||||
changeReferences = false;
|
||||
highlights = new Set<string>();
|
||||
wasExpanded = new Set<UserData>();
|
||||
|
||||
dynamicColumnDefs: any[] = [];
|
||||
dynamicColumnIds: string[] = [];
|
||||
|
||||
expandedPerson: UserData;
|
||||
|
||||
@ViewChild(MatPaginator) _paginator: MatPaginator;
|
||||
|
||||
@ViewChild(MatSort) sort: MatSort;
|
||||
|
||||
isDetailRow = (row: DetailRow|UserData) => row.hasOwnProperty('detailRow');
|
||||
|
||||
constructor(public _peopleDatabase: PeopleDatabase) { }
|
||||
|
||||
ngOnInit() {
|
||||
@@ -55,6 +70,7 @@ export class TableDemo {
|
||||
this.displayedColumns = ['userId', 'userName', 'progress', 'color'];
|
||||
this.dataSource = new PersonDataSource(this._peopleDatabase,
|
||||
this._paginator, this.sort);
|
||||
this.dataSourceWithDetails = new PersonDetailDataSource(this.dataSource);
|
||||
this._peopleDatabase.initialize();
|
||||
}
|
||||
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
|
||||
import {ChangeDetectionStrategy, Component, Directive, ViewEncapsulation} from '@angular/core';
|
||||
import {
|
||||
CdkHeaderRow,
|
||||
CdkRow,
|
||||
CDK_ROW_TEMPLATE,
|
||||
CdkRowDef,
|
||||
CdkHeaderRow,
|
||||
CdkHeaderRowDef,
|
||||
CdkRow,
|
||||
CdkRowDef,
|
||||
} from '@angular/cdk/table';
|
||||
|
||||
/** Workaround for https://github.com/angular/angular/issues/17849 */
|
||||
@@ -34,14 +34,16 @@ export class MatHeaderRowDef extends _MatHeaderRowDef { }
|
||||
|
||||
/**
|
||||
* Data row definition for the mat-table.
|
||||
* Captures the header row's template and other row properties such as the columns to display.
|
||||
* Captures the header row's template and other row properties such as the columns to display and
|
||||
* a when predicate that describes when this row should be used.
|
||||
*/
|
||||
@Directive({
|
||||
selector: '[matRowDef]',
|
||||
providers: [{provide: CdkRowDef, useExisting: MatRowDef}],
|
||||
inputs: ['columns: matRowDefColumns'],
|
||||
inputs: ['columns: matRowDefColumns', 'when: matRowDefWhen'],
|
||||
})
|
||||
export class MatRowDef extends _MatCdkRowDef { }
|
||||
export class MatRowDef<T> extends _MatCdkRowDef<T> {
|
||||
}
|
||||
|
||||
/** Header template container that contains the cell outlet. Adds the right class and role. */
|
||||
@Component({
|
||||
|
||||
+58
-12
@@ -1,4 +1,4 @@
|
||||
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
import {async, TestBed} from '@angular/core/testing';
|
||||
import {Component, ViewChild} from '@angular/core';
|
||||
import {DataSource} from '@angular/cdk/collections';
|
||||
import {BehaviorSubject} from 'rxjs/BehaviorSubject';
|
||||
@@ -7,22 +7,17 @@ import {MatTableModule} from './index';
|
||||
import {MatTable} from './table';
|
||||
|
||||
describe('MatTable', () => {
|
||||
let fixture: ComponentFixture<SimpleMatTableApp>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [MatTableModule],
|
||||
declarations: [SimpleMatTableApp],
|
||||
declarations: [MatTableApp, MatTableWithWhenRowApp],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SimpleMatTableApp);
|
||||
it('should be able to create a table with the right content and without when row', () => {
|
||||
let fixture = TestBed.createComponent(MatTableApp);
|
||||
fixture.detectChanges();
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create a table with the right content', () => {
|
||||
const tableElement = fixture.nativeElement.querySelector('.mat-table');
|
||||
const headerRow = tableElement.querySelectorAll('.mat-header-cell');
|
||||
expectTextContent(headerRow[0], 'Column A');
|
||||
@@ -30,12 +25,32 @@ describe('MatTable', () => {
|
||||
expectTextContent(headerRow[2], 'Column C');
|
||||
|
||||
const rows = tableElement.querySelectorAll('.mat-row');
|
||||
for (let i = 0; i < rows.length; i++) {
|
||||
|
||||
// First three rows use columns ['column_a', 'column_b', 'column_c']
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const cells = rows[i].querySelectorAll('.mat-cell');
|
||||
expectTextContent(cells[0], `a_${i + 1}`);
|
||||
expectTextContent(cells[1], `b_${i + 1}`);
|
||||
expectTextContent(cells[2], `c_${i + 1}`);
|
||||
}
|
||||
|
||||
// Fourth row uses a special when predicate to show a different colummn
|
||||
expect(rows[3].textContent.trim()).toBe('fourth_row');
|
||||
});
|
||||
|
||||
it('should create a table with special when row', () => {
|
||||
let fixture = TestBed.createComponent(MatTableWithWhenRowApp);
|
||||
fixture.detectChanges();
|
||||
|
||||
const tableElement = fixture.nativeElement.querySelector('.mat-table');
|
||||
const headerRow = tableElement.querySelectorAll('.mat-header-cell');
|
||||
expectTextContent(headerRow[0], 'Column A');
|
||||
|
||||
const rows = tableElement.querySelectorAll('.mat-row');
|
||||
expect(rows[0].textContent.trim()).toBe('a_1');
|
||||
expect(rows[1].textContent.trim()).toBe('a_2');
|
||||
expect(rows[2].textContent.trim()).toBe('a_3');
|
||||
expect(rows[3].textContent.trim()).toBe('fourth_row');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -60,7 +75,7 @@ class FakeDataSource extends DataSource<TestData> {
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
for (let i = 0; i < 3; i++) { this.addData(); }
|
||||
for (let i = 0; i < 4; i++) { this.addData(); }
|
||||
}
|
||||
|
||||
connect(): Observable<TestData[]> {
|
||||
@@ -101,15 +116,46 @@ class FakeDataSource extends DataSource<TestData> {
|
||||
<mat-cell *matCellDef="let row"> {{row.c}}</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="special_column">
|
||||
<mat-cell *matCellDef="let row"> fourth_row </mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<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-table>
|
||||
`
|
||||
})
|
||||
class SimpleMatTableApp {
|
||||
class MatTableApp {
|
||||
dataSource: FakeDataSource | null = new FakeDataSource();
|
||||
columnsToRender = ['column_a', 'column_b', 'column_c'];
|
||||
isFourthRow = (_rowData: TestData, i: number) => i == 3;
|
||||
|
||||
@ViewChild(MatTable) table: MatTable<TestData>;
|
||||
}
|
||||
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<mat-table [dataSource]="dataSource">
|
||||
<ng-container matColumnDef="column_a">
|
||||
<mat-header-cell *matHeaderCellDef> Column A</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row"> {{row.a}}</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="special_column">
|
||||
<mat-cell *matCellDef="let row"> fourth_row </mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<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-table>
|
||||
`
|
||||
})
|
||||
class MatTableWithWhenRowApp {
|
||||
dataSource: FakeDataSource | null = new FakeDataSource();
|
||||
isFourthRow = (_rowData: TestData, i: number) => i == 3;
|
||||
|
||||
@ViewChild(MatTable) table: MatTable<TestData>;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user