feat(table): add row when predicate (#6795)

This commit is contained in:
Andrew Seguin
2017-10-03 11:53:07 -07:00
committed by Kara
parent 36da4c3991
commit 0875b858d0
10 changed files with 416 additions and 51 deletions
+12 -3
View File
@@ -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) {
+16
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
}
+40
View File
@@ -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>
+13 -2
View File
@@ -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;
}
+16
View File
@@ -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 -6
View File
@@ -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
View File
@@ -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>;
}