Skip to content

Commit cf76707

Browse files
andrewseguinmmalerba
authored andcommitted
feat(table): add text column for simple columns (#14841)
* feat(table): add text column for simple columns * minor review changes * fix lint * add note regarding minification * add static true to query for column def * change cell query to static true * revert static query in cell; directly provide cells to column * new goldens
1 parent 021b85a commit cf76707

24 files changed

+713
-237
lines changed

src/cdk/table/cell.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export interface CellDef {
2222
*/
2323
@Directive({selector: '[cdkCellDef]'})
2424
export class CdkCellDef implements CellDef {
25-
constructor(/** @docs-private */ public template: TemplateRef<any>) { }
25+
constructor(/** @docs-private */ public template: TemplateRef<any>) {}
2626
}
2727

2828
/**
@@ -31,7 +31,7 @@ export class CdkCellDef implements CellDef {
3131
*/
3232
@Directive({selector: '[cdkHeaderCellDef]'})
3333
export class CdkHeaderCellDef implements CellDef {
34-
constructor(/** @docs-private */ public template: TemplateRef<any>) { }
34+
constructor(/** @docs-private */ public template: TemplateRef<any>) {}
3535
}
3636

3737
/**
@@ -40,13 +40,13 @@ export class CdkHeaderCellDef implements CellDef {
4040
*/
4141
@Directive({selector: '[cdkFooterCellDef]'})
4242
export class CdkFooterCellDef implements CellDef {
43-
constructor(/** @docs-private */ public template: TemplateRef<any>) { }
43+
constructor(/** @docs-private */ public template: TemplateRef<any>) {}
4444
}
4545

4646
// Boilerplate for applying mixins to CdkColumnDef.
4747
/** @docs-private */
4848
export class CdkColumnDefBase {}
49-
export const _CdkColumnDefBase: CanStickCtor & typeof CdkColumnDefBase =
49+
export const _CdkColumnDefBase: CanStickCtor&typeof CdkColumnDefBase =
5050
mixinHasStickyInput(CdkColumnDefBase);
5151

5252
/**
@@ -56,19 +56,20 @@ export const _CdkColumnDefBase: CanStickCtor & typeof CdkColumnDefBase =
5656
@Directive({
5757
selector: '[cdkColumnDef]',
5858
inputs: ['sticky'],
59-
providers: [{
60-
provide: 'MAT_SORT_HEADER_COLUMN_DEF',
61-
useExisting: CdkColumnDef
62-
}],
59+
providers: [{provide: 'MAT_SORT_HEADER_COLUMN_DEF', useExisting: CdkColumnDef}],
6360
})
6461
export class CdkColumnDef extends _CdkColumnDefBase implements CanStick {
6562
/** Unique name for this column. */
6663
@Input('cdkColumnDef')
67-
get name(): string { return this._name; }
64+
get name(): string {
65+
return this._name;
66+
}
6867
set name(name: string) {
6968
// If the directive is set without a name (updated programatically), then this setter will
7069
// trigger with an empty string and should not overwrite the programatically set value.
71-
if (!name) { return; }
70+
if (!name) {
71+
return;
72+
}
7273

7374
this._name = name;
7475
this.cssClassFriendlyName = name.replace(/[^a-z0-9_-]/ig, '-');
@@ -81,7 +82,9 @@ export class CdkColumnDef extends _CdkColumnDefBase implements CanStick {
8182
* has been changed.
8283
*/
8384
@Input('stickyEnd')
84-
get stickyEnd(): boolean { return this._stickyEnd; }
85+
get stickyEnd(): boolean {
86+
return this._stickyEnd;
87+
}
8588
set stickyEnd(v: boolean) {
8689
const prevValue = this._stickyEnd;
8790
this._stickyEnd = coerceBooleanProperty(v);

src/cdk/table/public-api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export * from './row';
1212
export * from './table-module';
1313
export * from './sticky-styler';
1414
export * from './can-stick';
15+
export * from './text-column';
1516

1617
/** Re-export DataSource for a more intuitive experience for users of just the table. */
1718
export {DataSource} from '@angular/cdk/collections';

src/cdk/table/table-errors.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,3 +56,11 @@ export function getTableMissingRowDefsError() {
5656
export function getTableUnknownDataSourceError() {
5757
return Error(`Provided data source did not match an array, Observable, or DataSource`);
5858
}
59+
60+
/**
61+
* Returns an error to be thrown when the text column cannot find a parent table to inject.
62+
* @docs-private
63+
*/
64+
export function getTableTextColumnMissingParentTableError() {
65+
return Error(`Text column could not find a parent table for registration.`);
66+
}

src/cdk/table/table-module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
CdkColumnDef, CdkHeaderCellDef, CdkHeaderCell, CdkCell, CdkCellDef,
1818
CdkFooterCellDef, CdkFooterCell
1919
} from './cell';
20+
import {CdkTextColumn} from './text-column';
2021

2122
const EXPORTED_DECLARATIONS = [
2223
CdkTable,
@@ -37,6 +38,7 @@ const EXPORTED_DECLARATIONS = [
3738
DataRowOutlet,
3839
HeaderRowOutlet,
3940
FooterRowOutlet,
41+
CdkTextColumn,
4042
];
4143

4244
@NgModule({

src/cdk/table/table.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2326,7 +2326,7 @@ function getActualTableContent(tableElement: Element): string[][] {
23262326
return actualTableContent.map(row => row.map(cell => cell.textContent!.trim()));
23272327
}
23282328

2329-
function expectTableToMatchContent(tableElement: Element, expected: any[]) {
2329+
export function expectTableToMatchContent(tableElement: Element, expected: any[]) {
23302330
const missedExpectations: string[] = [];
23312331
function checkCellContent(actualCell: string, expectedCell: string) {
23322332
if (actualCell !== expectedCell) {

src/cdk/table/text-column.spec.ts

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
import {Component} from '@angular/core';
2+
import {async, ComponentFixture, TestBed} from '@angular/core/testing';
3+
4+
import {getTableTextColumnMissingParentTableError} from './table-errors';
5+
import {CdkTableModule} from './table-module';
6+
import {expectTableToMatchContent} from './table.spec';
7+
import {TEXT_COLUMN_OPTIONS, TextColumnOptions} from './text-column';
8+
9+
10+
describe('CdkTextColumn', () => {
11+
let fixture: ComponentFixture<BasicTextColumnApp>;
12+
let component: BasicTextColumnApp;
13+
let tableElement: HTMLElement;
14+
15+
beforeEach(async(() => {
16+
TestBed
17+
.configureTestingModule({
18+
imports: [CdkTableModule],
19+
declarations: [
20+
BasicTextColumnApp,
21+
MissingTableApp,
22+
],
23+
})
24+
.compileComponents();
25+
}));
26+
27+
beforeEach(() => {
28+
fixture = TestBed.createComponent(BasicTextColumnApp);
29+
component = fixture.componentInstance;
30+
fixture.detectChanges();
31+
32+
tableElement = fixture.nativeElement.querySelector('.cdk-table');
33+
});
34+
35+
it('should render the basic columns', () => {
36+
expectTableToMatchContent(tableElement, [
37+
['PropertyA', 'PropertyB', 'PropertyC'],
38+
['a_1', 'b_1', 'c_1'],
39+
['a_2', 'b_2', 'c_2'],
40+
]);
41+
});
42+
43+
it('should throw an error if the text column is not in the content of a table', () => {
44+
expect(() => TestBed.createComponent(MissingTableApp).detectChanges())
45+
.toThrowError(getTableTextColumnMissingParentTableError().message);
46+
});
47+
48+
it('should allow for alternate header text', () => {
49+
component.headerTextB = 'column-b';
50+
fixture.detectChanges();
51+
52+
expectTableToMatchContent(tableElement, [
53+
['PropertyA', 'column-b', 'PropertyC'],
54+
['a_1', 'b_1', 'c_1'],
55+
['a_2', 'b_2', 'c_2'],
56+
]);
57+
});
58+
59+
it('should allow for custom data accessor', () => {
60+
component.dataAccessorA = (data: TestData) => data.propertyA + '!';
61+
fixture.detectChanges();
62+
63+
expectTableToMatchContent(tableElement, [
64+
['PropertyA', 'PropertyB', 'PropertyC'],
65+
['a_1!', 'b_1', 'c_1'],
66+
['a_2!', 'b_2', 'c_2'],
67+
]);
68+
});
69+
70+
it('should allow for custom data accessor', () => {
71+
component.dataAccessorA = (data: TestData) => data.propertyA + '!';
72+
fixture.detectChanges();
73+
74+
expectTableToMatchContent(tableElement, [
75+
['PropertyA', 'PropertyB', 'PropertyC'],
76+
['a_1!', 'b_1', 'c_1'],
77+
['a_2!', 'b_2', 'c_2'],
78+
]);
79+
});
80+
81+
it('should update values when data changes', () => {
82+
component.data = [
83+
{propertyA: 'changed-a_1', propertyB: 'b_1', propertyC: 'c_1'},
84+
{propertyA: 'changed-a_2', propertyB: 'b_2', propertyC: 'c_2'},
85+
];
86+
fixture.detectChanges();
87+
88+
expectTableToMatchContent(tableElement, [
89+
['PropertyA', 'PropertyB', 'PropertyC'],
90+
['changed-a_1', 'b_1', 'c_1'],
91+
['changed-a_2', 'b_2', 'c_2'],
92+
]);
93+
});
94+
95+
describe('with options', () => {
96+
function createTestComponent(options: TextColumnOptions<any>) {
97+
// Reset the previously configured testing module to be able set new providers.
98+
// The testing module has been initialized in the root describe group for the ripples.
99+
TestBed.resetTestingModule();
100+
TestBed.configureTestingModule({
101+
imports: [CdkTableModule],
102+
declarations: [BasicTextColumnApp],
103+
providers: [{provide: TEXT_COLUMN_OPTIONS, useValue: options}]
104+
});
105+
106+
fixture = TestBed.createComponent(BasicTextColumnApp);
107+
fixture.detectChanges();
108+
109+
tableElement = fixture.nativeElement.querySelector('.cdk-table');
110+
}
111+
112+
it('should be able to provide a header text transformation', () => {
113+
const defaultHeaderTextTransform = (name: string) => `${name}!`;
114+
createTestComponent({defaultHeaderTextTransform});
115+
116+
expectTableToMatchContent(tableElement, [
117+
['propertyA!', 'propertyB!', 'propertyC!'],
118+
['a_1', 'b_1', 'c_1'],
119+
['a_2', 'b_2', 'c_2'],
120+
]);
121+
});
122+
123+
it('should be able to provide a general data accessor', () => {
124+
const defaultDataAccessor = (data: TestData, name: string) => {
125+
switch (name) {
126+
case 'propertyA':
127+
return `A: ${data.propertyA}`;
128+
case 'propertyB':
129+
return `B: ${data.propertyB}`;
130+
case 'propertyC':
131+
return `C: ${data.propertyC}`;
132+
default:
133+
return '';
134+
}
135+
};
136+
createTestComponent({defaultDataAccessor});
137+
138+
expectTableToMatchContent(tableElement, [
139+
['PropertyA', 'PropertyB', 'PropertyC'],
140+
['A: a_1', 'B: b_1', 'C: c_1'],
141+
['A: a_2', 'B: b_2', 'C: c_2'],
142+
]);
143+
});
144+
});
145+
});
146+
147+
interface TestData {
148+
propertyA: string;
149+
propertyB: string;
150+
propertyC: string;
151+
}
152+
153+
@Component({
154+
template: `
155+
<cdk-table [dataSource]="data">
156+
<cdk-text-column name="propertyA" [dataAccessor]="dataAccessorA"></cdk-text-column>
157+
<cdk-text-column name="propertyB" [headerText]="headerTextB"></cdk-text-column>
158+
<cdk-text-column name="propertyC" [justify]="justifyC"></cdk-text-column>
159+
160+
<cdk-header-row *cdkHeaderRowDef="displayedColumns"></cdk-header-row>
161+
<cdk-row *cdkRowDef="let row; columns: displayedColumns"></cdk-row>
162+
</cdk-table>
163+
`
164+
})
165+
class BasicTextColumnApp {
166+
displayedColumns = ['propertyA', 'propertyB', 'propertyC'];
167+
168+
data: TestData[] = [
169+
{propertyA: 'a_1', propertyB: 'b_1', propertyC: 'c_1'},
170+
{propertyA: 'a_2', propertyB: 'b_2', propertyC: 'c_2'},
171+
];
172+
173+
headerTextB: string;
174+
dataAccessorA: (data: TestData) => string;
175+
justifyC = 'start';
176+
}
177+
178+
@Component({
179+
template: `
180+
<cdk-text-column name="column-a"></cdk-text-column>
181+
`
182+
})
183+
class MissingTableApp {
184+
}

0 commit comments

Comments
 (0)