Skip to content

Commit b1727c3

Browse files
committed
feat(table): allow data input to be array, stream
1 parent 4523556 commit b1727c3

File tree

12 files changed

+513
-111
lines changed

12 files changed

+513
-111
lines changed

src/cdk/table/table-errors.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,11 @@ export function getTableMissingRowDefsError() {
4747
return Error('Missing definitions for header and row, ' +
4848
'cannot determine which columns should be rendered.');
4949
}
50+
51+
/**
52+
* Returns an error to be thrown when the data source does not match the compatible types.
53+
* @docs-private
54+
*/
55+
export function getTableUnknownDataSourceError() {
56+
return Error(`Provided data source did not match an array, Observable, or DataSource`);
57+
}

src/cdk/table/table.spec.ts

Lines changed: 148 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import {
1111
getTableMissingMatchingRowDefError,
1212
getTableMissingRowDefsError,
1313
getTableMultipleDefaultRowDefsError,
14-
getTableUnknownColumnError
14+
getTableUnknownColumnError,
15+
getTableUnknownDataSourceError
1516
} from './table-errors';
1617
import {CdkHeaderRowDef, CdkRowDef} from './row';
1718
import {CdkColumnDef} from './cell';
@@ -45,6 +46,7 @@ describe('CdkTable', () => {
4546
BooleanRowCdkTableApp,
4647
WrapperCdkTableApp,
4748
OuterTableApp,
49+
CdkTableWithDifferentDataInputsApp,
4850
],
4951
}).compileComponents();
5052
}));
@@ -113,6 +115,111 @@ describe('CdkTable', () => {
113115
});
114116
});
115117

118+
describe('with different data inputs other than data source', () => {
119+
let dataInputFixture: ComponentFixture<CdkTableWithDifferentDataInputsApp>;
120+
let dataInputComponent: CdkTableWithDifferentDataInputsApp;
121+
let dataInputTableElement: HTMLElement;
122+
123+
let baseData: TestData[] = [
124+
{a: 'a_1', b: 'b_1', c: 'c_1'},
125+
{a: 'a_2', b: 'b_2', c: 'c_2'},
126+
{a: 'a_3', b: 'b_3', c: 'c_3'},
127+
];
128+
129+
beforeEach(() => {
130+
dataInputFixture = TestBed.createComponent(CdkTableWithDifferentDataInputsApp);
131+
dataInputComponent = dataInputFixture.componentInstance;
132+
dataInputFixture.detectChanges();
133+
134+
dataInputTableElement = dataInputFixture.nativeElement.querySelector('cdk-table');
135+
});
136+
137+
it('should render with data array input', () => {
138+
const data = baseData.slice();
139+
dataInputComponent.dataSource = data;
140+
dataInputFixture.detectChanges();
141+
142+
const expectedRender = [
143+
['Column A', 'Column B', 'Column C'],
144+
['a_1', 'b_1', 'c_1'],
145+
['a_2', 'b_2', 'c_2'],
146+
['a_3', 'b_3', 'c_3'],
147+
];
148+
expectTableToMatchContent(dataInputTableElement, expectedRender);
149+
150+
// Push data to the array but neglect to tell the table, should be no change
151+
data.push({a: 'a_4', b: 'b_4', c: 'c_4'});
152+
expectTableToMatchContent(dataInputTableElement, expectedRender);
153+
154+
// Notify table of the change, expect another row
155+
dataInputComponent.table.renderRows();
156+
dataInputFixture.detectChanges();
157+
expectedRender.push(['a_4', 'b_4', 'c_4']);
158+
expectTableToMatchContent(dataInputTableElement, expectedRender);
159+
160+
// Remove a row and expect the change in rows
161+
data.pop();
162+
expectedRender.pop();
163+
dataInputComponent.table.renderRows();
164+
expectTableToMatchContent(dataInputTableElement, expectedRender);
165+
166+
// Remove the data input entirely and expect no rows - just header.
167+
dataInputComponent.dataSource = null;
168+
dataInputFixture.detectChanges();
169+
expectTableToMatchContent(dataInputTableElement, [expectedRender[0]]);
170+
171+
// Add back the data to verify that it renders rows
172+
dataInputComponent.dataSource = data;
173+
dataInputFixture.detectChanges();
174+
expectTableToMatchContent(dataInputTableElement, expectedRender);
175+
});
176+
177+
it('should render with data stream input', () => {
178+
const data = baseData.slice();
179+
const stream = new BehaviorSubject<TestData[]>(data);
180+
dataInputComponent.dataSource = stream;
181+
dataInputFixture.detectChanges();
182+
183+
const expectedRender = [
184+
['Column A', 'Column B', 'Column C'],
185+
['a_1', 'b_1', 'c_1'],
186+
['a_2', 'b_2', 'c_2'],
187+
['a_3', 'b_3', 'c_3'],
188+
];
189+
expectTableToMatchContent(dataInputTableElement, expectedRender);
190+
191+
// Push data to the array
192+
data.push({a: 'a_4', b: 'b_4', c: 'c_4'});
193+
expectedRender.push(['a_4', 'b_4', 'c_4']);
194+
stream.next(data);
195+
dataInputFixture.detectChanges();
196+
expectTableToMatchContent(dataInputTableElement, expectedRender);
197+
198+
// Remove a row and expect the change in rows
199+
data.pop();
200+
expectedRender.pop();
201+
stream.next(data);
202+
expectTableToMatchContent(dataInputTableElement, expectedRender);
203+
204+
// Remove the data input entirely and expect no rows - just header.
205+
dataInputComponent.dataSource = null;
206+
dataInputFixture.detectChanges();
207+
expectTableToMatchContent(dataInputTableElement, [expectedRender[0]]);
208+
209+
// Add back the data to verify that it renders rows
210+
dataInputComponent.dataSource = stream;
211+
dataInputFixture.detectChanges();
212+
expectTableToMatchContent(dataInputTableElement, expectedRender);
213+
});
214+
215+
it('should throw an error if the data source is not valid', () => {
216+
dataInputComponent.dataSource = {invalid: 'dataSource'};
217+
218+
expect(() => dataInputFixture.detectChanges())
219+
.toThrowError(getTableUnknownDataSourceError().message);
220+
});
221+
});
222+
116223
it('should render cells even if row data is falsy', () => {
117224
const booleanRowCdkTableAppFixture = TestBed.createComponent(BooleanRowCdkTableApp);
118225
const booleanRowCdkTableElement =
@@ -720,6 +827,36 @@ class SimpleCdkTableApp {
720827
@ViewChild(CdkTable) table: CdkTable<TestData>;
721828
}
722829

830+
@Component({
831+
template: `
832+
<cdk-table [dataSource]="dataSource">
833+
<ng-container cdkColumnDef="column_a">
834+
<cdk-header-cell *cdkHeaderCellDef> Column A</cdk-header-cell>
835+
<cdk-cell *cdkCellDef="let row"> {{row.a}}</cdk-cell>
836+
</ng-container>
837+
838+
<ng-container cdkColumnDef="column_b">
839+
<cdk-header-cell *cdkHeaderCellDef> Column B</cdk-header-cell>
840+
<cdk-cell *cdkCellDef="let row"> {{row.b}}</cdk-cell>
841+
</ng-container>
842+
843+
<ng-container cdkColumnDef="column_c">
844+
<cdk-header-cell *cdkHeaderCellDef> Column C</cdk-header-cell>
845+
<cdk-cell *cdkCellDef="let row"> {{row.c}}</cdk-cell>
846+
</ng-container>
847+
848+
<cdk-header-row *cdkHeaderRowDef="columnsToRender"></cdk-header-row>
849+
<cdk-row *cdkRowDef="let row; columns: columnsToRender"></cdk-row>
850+
</cdk-table>
851+
`
852+
})
853+
class CdkTableWithDifferentDataInputsApp {
854+
dataSource: DataSource<TestData> | Observable<TestData[]> | TestData[] | any = null;
855+
columnsToRender = ['column_a', 'column_b', 'column_c'];
856+
857+
@ViewChild(CdkTable) table: CdkTable<TestData>;
858+
}
859+
723860
@Component({
724861
template: `
725862
<cdk-table [dataSource]="dataSource">
@@ -1186,6 +1323,9 @@ function expectTableToMatchContent(tableElement: Element, expectedTableContent:
11861323
}
11871324
}
11881325

1326+
// Copy the expected data array to avoid mutating the test's array
1327+
expectedTableContent = expectedTableContent.slice();
1328+
11891329
// Check header cells
11901330
const expectedHeaderContent = expectedTableContent.shift();
11911331
getHeaderCells(tableElement).forEach((cell, index) => {
@@ -1196,7 +1336,13 @@ function expectTableToMatchContent(tableElement: Element, expectedTableContent:
11961336
});
11971337

11981338
// Check data row cells
1199-
getRows(tableElement).forEach((row, rowIndex) => {
1339+
const rows = getRows(tableElement);
1340+
if (rows.length !== expectedTableContent.length) {
1341+
missedExpectations.push(
1342+
`Expected ${expectedTableContent.length} rows but found ${rows.length}`);
1343+
fail(missedExpectations.join('\n'));
1344+
}
1345+
rows.forEach((row, rowIndex) => {
12001346
getCells(row).forEach((cell, cellIndex) => {
12011347
const expected = expectedTableContent.length ?
12021348
expectedTableContent[rowIndex][cellIndex] :

0 commit comments

Comments
 (0)