Skip to content

Commit 097a2ff

Browse files
committed
feat(table): add more magical data source
1 parent deca032 commit 097a2ff

File tree

9 files changed

+297
-122
lines changed

9 files changed

+297
-122
lines changed

src/cdk/rxjs/rx-operators.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {Observable, ObservableInput} from 'rxjs/Observable';
9+
// Subscribable needs to be imported because combineLatest has or is using it, but it does not need
10+
// to be used in any of the explicit typings in this file.
11+
// tslint:disable-next-line:no-unused-variable
12+
import {Observable, ObservableInput, Subscribable} from 'rxjs/Observable';
1013
import {PartialObserver} from 'rxjs/Observer';
1114
import {Subscription} from 'rxjs/Subscription';
1215
import {IScheduler} from 'rxjs/Scheduler';
@@ -22,6 +25,7 @@ import {startWith as startWithOperator} from 'rxjs/operator/startWith';
2225
import {debounceTime as debounceTimeOperator} from 'rxjs/operator/debounceTime';
2326
import {auditTime as auditTimeOperator} from 'rxjs/operator/auditTime';
2427
import {takeUntil as takeUntilOperator} from 'rxjs/operator/takeUntil';
28+
import {combineLatest as combineLatestOperator} from 'rxjs/operator/combineLatest';
2529

2630
/**
2731
* Represents a strongly-typed chain of RxJS operators.
@@ -71,6 +75,9 @@ export interface StrictRxChain<T> {
7175

7276
call(operator: takeUntilOperatorType<T>, notifier: Observable<any>): StrictRxChain<T>;
7377

78+
call<T2>(operator: combineLatestOperatorType<T, T2>,
79+
v2: ObservableInput<T2>): StrictRxChain<[T, T2]>;
80+
7481
subscribe(fn: (t: T) => void): Subscription;
7582

7683
result(): Observable<T>;
@@ -89,6 +96,7 @@ export class StartWithBrand { private _; }
8996
export class DebounceTimeBrand { private _; }
9097
export class AuditTimeBrand { private _; }
9198
export class TakeUntilBrand { private _; }
99+
export class CombineLatestBrand { private _; }
92100

93101

94102
export type finallyOperatorType<T> = typeof _finallyOperator & FinallyBrand;
@@ -103,8 +111,9 @@ export type startWithOperatorType<T> = typeof startWithOperator & StartWithBrand
103111
export type debounceTimeOperatorType<T> = typeof debounceTimeOperator & DebounceTimeBrand;
104112
export type auditTimeOperatorType<T> = typeof auditTimeOperator & AuditTimeBrand;
105113
export type takeUntilOperatorType<T> = typeof takeUntilOperator & TakeUntilBrand;
114+
export type combineLatestOperatorType<T, R> = typeof combineLatestOperator & CombineLatestBrand;
106115

107-
// We add `Function` to the type intersection to make this nomically different from
116+
// We add `Function` to the type intersection to make this nominally different from
108117
// `finallyOperatorType` while still being structurally the same. Without this, TypeScript tries to
109118
// reduce `typeof _finallyOperator & FinallyBrand` to `finallyOperatorType<T>` and then fails
110119
// because `T` isn't known.
@@ -122,3 +131,5 @@ export const debounceTime =
122131
debounceTimeOperator as typeof debounceTimeOperator & DebounceTimeBrand & Function;
123132
export const auditTime = auditTimeOperator as typeof auditTimeOperator & AuditTimeBrand & Function;
124133
export const takeUntil = takeUntilOperator as typeof takeUntilOperator & TakeUntilBrand & Function;
134+
export const combineLatest =
135+
combineLatestOperator as typeof combineLatestOperator & CombineLatestBrand & Function;

src/demo-app/table/table-demo.html

Lines changed: 11 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -19,124 +19,29 @@
1919
</div>
2020
</div>
2121

22-
<md-card class="demo-table-card">
23-
<h3>
24-
CdkTable With Dynamic Column Def
25-
<div>
26-
<button md-raised-button
27-
(click)="addDynamicColumnDef()"
28-
[disabled]="dynamicColumnIds.length >= 4">
29-
Add Column Def
30-
</button>
31-
<button md-raised-button
32-
(click)="removeDynamicColumnDef()"
33-
[disabled]="dynamicColumnIds.length == 0">
34-
Remove Column Def
35-
</button>
36-
</div>
37-
</h3>
38-
39-
<cdk-table [dataSource]="dataSource">
40-
<ng-container [cdkColumnDef]="column.id" *ngFor="let column of dynamicColumnDefs">
41-
<cdk-header-cell *cdkHeaderCellDef> {{column.headerText}} </cdk-header-cell>
42-
<cdk-cell *cdkCellDef="let row"> {{row[column.property]}} </cdk-cell>
43-
</ng-container>
44-
45-
<cdk-header-row *cdkHeaderRowDef="dynamicColumnIds"></cdk-header-row>
46-
<cdk-row *cdkRowDef="let row; columns: dynamicColumnIds;"></cdk-row>
47-
</cdk-table>
48-
</md-card>
49-
50-
<md-card class="demo-table-card">
51-
<h3>CdkTable Example</h3>
52-
53-
<div class="demo-highlighter">
54-
Highlight:
55-
<md-checkbox (change)="toggleHighlight('first', $event.checked)">First Row</md-checkbox>
56-
<md-checkbox (change)="toggleHighlight('last', $event.checked)">Last Row</md-checkbox>
57-
<md-checkbox (change)="toggleHighlight('even', $event.checked)">Even Rows</md-checkbox>
58-
<md-checkbox (change)="toggleHighlight('odd', $event.checked)">Odd Rows</md-checkbox>
59-
</div>
60-
61-
<cdk-table #table mdSort
62-
[dataSource]="dataSource"
63-
[trackBy]="userTrackBy">
64-
65-
<!-- Column Definition: ID -->
66-
<ng-container cdkColumnDef="userId">
67-
<cdk-header-cell *cdkHeaderCellDef
68-
md-sort-header arrowPosition="before">
69-
ID
70-
</cdk-header-cell>
71-
<cdk-cell *cdkCellDef="let row"> {{row.id}} </cdk-cell>
72-
</ng-container>
73-
74-
<!-- Column Definition: Progress -->
75-
<ng-container cdkColumnDef="progress">
76-
<cdk-header-cell *cdkHeaderCellDef
77-
md-sort-header start="desc">
78-
Progress
79-
</cdk-header-cell>
80-
<cdk-cell *cdkCellDef="let row">
81-
<div class="demo-progress-stat">{{row.progress}}%</div>
82-
<div class="demo-progress-indicator-container">
83-
<div class="demo-progress-indicator"
84-
[style.background]="row.progress > 50 ? 'green' : 'red'"
85-
[style.opacity]="getOpacity(row.progress)"
86-
[style.width.%]="row.progress"></div>
87-
</div>
88-
</cdk-cell>
89-
</ng-container>
90-
91-
<!-- Column Definition: Name -->
92-
<ng-container cdkColumnDef="userName">
93-
<cdk-header-cell *cdkHeaderCellDef md-sort-header>
94-
Name
95-
</cdk-header-cell>
96-
<cdk-cell *cdkCellDef="let row"> {{row.name}} </cdk-cell>
97-
</ng-container>
98-
99-
<!-- Column Definition: Color -->
100-
<ng-container cdkColumnDef="color">
101-
<cdk-header-cell *cdkHeaderCellDef
102-
md-sort-header disableClear>
103-
Color
104-
</cdk-header-cell>
105-
<cdk-cell *cdkCellDef="let row" [style.color]="row.color"> {{row.color}} </cdk-cell>
106-
</ng-container>
107-
108-
<cdk-header-row *cdkHeaderRowDef="displayedColumns"></cdk-header-row>
109-
<cdk-row *cdkRowDef="let row; columns: displayedColumns;
110-
let first = first; let last = last; let even = even; let odd = odd"
111-
[ngClass]="{
112-
'demo-row-highlight-first': highlights.has('first') && first,
113-
'demo-row-highlight-last': highlights.has('last') && last,
114-
'demo-row-highlight-even': highlights.has('even') && even,
115-
'demo-row-highlight-odd': highlights.has('odd') && odd
116-
}">
117-
</cdk-row>
118-
</cdk-table>
119-
</md-card>
120-
12122
<h3>MdTable Example</h3>
12223

24+
<md-form-field>
25+
<input mdInput [formControl]="filter">
26+
</md-form-field>
27+
12328
<div class="demo-table-container demo-mat-table-example mat-elevation-z4">
12429

12530
<table-header-demo (shiftColumns)="displayedColumns.push(displayedColumns.shift())"
12631
(toggleColorColumn)="toggleColorColumn()">
12732
</table-header-demo>
12833

129-
<md-table [dataSource]="dataSource" [trackBy]="userTrackBy">
34+
<md-table [dataSource]="dataSource" [trackBy]="userTrackBy" mdSort>
13035

13136
<!-- Column Definition: ID -->
13237
<ng-container cdkColumnDef="userId">
133-
<md-header-cell *mdHeaderCellDef> ID </md-header-cell>
38+
<md-header-cell *mdHeaderCellDef md-sort-header> ID </md-header-cell>
13439
<md-cell *mdCellDef="let row"> {{row.id}} </md-cell>
13540
</ng-container>
13641

13742
<!-- Column Definition: Progress -->
13843
<ng-container mdColumnDef="progress">
139-
<md-header-cell *mdHeaderCellDef> Progress </md-header-cell>
44+
<md-header-cell *mdHeaderCellDef md-sort-header> Progress </md-header-cell>
14045
<md-cell *mdCellDef="let row">
14146
<div class="demo-progress-stat">{{row.progress}}%</div>
14247
<div class="demo-progress-indicator-container">
@@ -150,13 +55,13 @@ <h3>MdTable Example</h3>
15055

15156
<!-- Column Definition: Name -->
15257
<ng-container mdColumnDef="userName">
153-
<md-header-cell *mdHeaderCellDef> Name </md-header-cell>
58+
<md-header-cell *mdHeaderCellDef md-sort-header> Name </md-header-cell>
15459
<md-cell *mdCellDef="let row"> {{row.name}} </md-cell>
15560
</ng-container>
15661

15762
<!-- Column Definition: Color -->
15863
<ng-container mdColumnDef="color">
159-
<md-header-cell *mdHeaderCellDef>Color</md-header-cell>
64+
<md-header-cell *mdHeaderCellDef md-sort-header>Color</md-header-cell>
16065
<md-cell *mdCellDef="let row" [style.color]="row.color"> {{row.color}} </md-cell>
16166
</ng-container>
16267

@@ -165,8 +70,7 @@ <h3>MdTable Example</h3>
16570

16671
</md-table>
16772

168-
<md-paginator #paginator
169-
[length]="_peopleDatabase.data.length"
170-
[pageSizeOptions]="[5, 10, 25, 100]">
73+
<md-paginator [pageSizeOptions]="[10, 25, 100]"
74+
[tableDataSource]="dataSource">
17175
</md-paginator>
17276
</div>

src/demo-app/table/table-demo.ts

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import {Component, ViewChild} from '@angular/core';
22
import {PeopleDatabase, UserData} from './people-database';
3-
import {PersonDataSource} from './person-data-source';
4-
import {MdPaginator, MdSort} from '@angular/material';
3+
import {MdPaginator, MdSort, MdTableDataSource} from '@angular/material';
4+
import {FormControl} from '@angular/forms';
5+
import 'rxjs/add/operator/debounceTime';
6+
57

68
export type UserProperties = 'userId' | 'userName' | 'progress' | 'color' | undefined;
79

@@ -16,23 +18,32 @@ const properties = ['id', 'name', 'progress', 'color'];
1618
styleUrls: ['table-demo.css'],
1719
})
1820
export class TableDemo {
19-
dataSource: PersonDataSource | null;
20-
displayedColumns: UserProperties[] = [];
21+
dataSource: MdTableDataSource<UserData>|null;
22+
displayedColumns: UserProperties[] = ['userId', 'userName', 'progress', 'color'];
2123
trackByStrategy: TrackByStrategy = 'reference';
2224
changeReferences = false;
2325
highlights = new Set<string>();
2426

27+
filter = new FormControl();
28+
2529
dynamicColumnDefs: any[] = [];
2630
dynamicColumnIds: string[] = [];
2731

2832
@ViewChild(MdPaginator) _paginator: MdPaginator;
2933

30-
@ViewChild(MdSort) sort: MdSort;
31-
32-
constructor(public _peopleDatabase: PeopleDatabase) { }
33-
34-
ngOnInit() {
35-
this.connect();
34+
constructor(public _peopleDatabase: PeopleDatabase) {
35+
this.dataSource = new MdTableDataSource(this._peopleDatabase.data);
36+
this.dataSource.dataAccessor = (data: UserData, property: string) => {
37+
switch (property) {
38+
case 'userId': return +data.id;
39+
case 'userName': return data.name;
40+
case 'progress': return +data.progress;
41+
case 'color': return data.color;
42+
default: return '';
43+
}
44+
};
45+
this.dataSource.filterTermAccessor = (data: UserData) => data.name;
46+
this.filter.valueChanges.subscribe(filter => this.dataSource!.filter = filter);
3647
}
3748

3849
addDynamicColumnDef() {
@@ -53,13 +64,11 @@ export class TableDemo {
5364

5465
connect() {
5566
this.displayedColumns = ['userId', 'userName', 'progress', 'color'];
56-
this.dataSource = new PersonDataSource(this._peopleDatabase,
57-
this._paginator, this.sort);
5867
this._peopleDatabase.initialize();
68+
this.dataSource!.data = this._peopleDatabase.data;
5969
}
6070

6171
disconnect() {
62-
this.dataSource = null;
6372
this.displayedColumns = [];
6473
}
6574

src/lib/core/rxjs/rx-operators.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ export {
2020
DebounceTimeBrand,
2121
AuditTimeBrand,
2222
TakeUntilBrand,
23+
CombineLatestBrand,
2324
finallyOperatorType,
2425
catchOperatorType,
2526
doOperatorType,
@@ -32,6 +33,7 @@ export {
3233
debounceTimeOperatorType,
3334
auditTimeOperatorType,
3435
takeUntilOperatorType,
36+
combineLatestOperatorType,
3537
finallyOperator,
3638
catchOperator,
3739
doOperator,
@@ -44,4 +46,5 @@ export {
4446
debounceTime,
4547
auditTime,
4648
takeUntil,
49+
combineLatest,
4750
} from '@angular/cdk/rxjs';

src/lib/paginator/paginator.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
import {MdPaginatorIntl} from './paginator-intl';
2121
import {MATERIAL_COMPATIBILITY_MODE} from '../core';
2222
import {Subscription} from 'rxjs/Subscription';
23+
import {MdTableDataSource} from '../table';
2324

2425
/** The default page size if there is no page size and there are no provided page size options. */
2526
const DEFAULT_PAGE_SIZE = 50;
@@ -98,6 +99,22 @@ export class MdPaginator implements OnInit, OnDestroy {
9899
}
99100
private _pageSizeOptions: number[] = [];
100101

102+
/**
103+
* An optional data source that can be provided if used with a table that uses an
104+
* MdTableDataSource. Assigns the data source after the paginator has been initialized with
105+
* its inputs.
106+
*/
107+
@Input()
108+
get tableDataSource(): MdTableDataSource<any>|null { return this._tableDataSource; }
109+
set tableDataSource(dataSource: MdTableDataSource<any>|null) {
110+
if (dataSource && dataSource instanceof MdTableDataSource &&
111+
this._tableDataSource != dataSource) {
112+
this._tableDataSource = dataSource;
113+
if (this._initialized) { dataSource.paginator = this; }
114+
}
115+
}
116+
private _tableDataSource: MdTableDataSource<any>|null;
117+
101118
/** Event emitted when the paginator changes the page size or page index. */
102119
@Output() page = new EventEmitter<PageEvent>();
103120

@@ -112,10 +129,17 @@ export class MdPaginator implements OnInit, OnDestroy {
112129
ngOnInit() {
113130
this._initialized = true;
114131
this._updateDisplayedPageSizeOptions();
132+
133+
if (this.tableDataSource) {
134+
this.tableDataSource.paginator = this;
135+
}
115136
}
116137

117138
ngOnDestroy() {
118139
this._intlChanges.unsubscribe();
140+
if (this.tableDataSource) {
141+
this.tableDataSource.paginator = null;
142+
}
119143
}
120144

121145
/** Advances to the next page if it exists. */

src/lib/sort/sort.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {Directive, EventEmitter, Input, Output} from '@angular/core';
1010
import {coerceBooleanProperty} from '@angular/cdk/coercion';
1111
import {SortDirection} from './sort-direction';
1212
import {getMdSortDuplicateMdSortableIdError, getMdSortHeaderMissingIdError} from './sort-errors';
13+
import {MdTable, MdTableDataSource} from '../table';
1314

1415
export interface MdSortable {
1516
id: string;
@@ -27,6 +28,13 @@ export interface Sort {
2728
selector: '[mdSort], [matSort]',
2829
})
2930
export class MdSort {
31+
/**
32+
* MdTableDataSource that has been assigned with an instance of the MdSort so that the data
33+
* source can respond to sort changes. This is relevant and assigned only in cases where the
34+
* MdSort has been applied to an MdTable with an MdTableDataSource.
35+
*/
36+
tableDataSource: MdTableDataSource<any>|null;
37+
3038
/** Collection of all registered sortables that this directive manages. */
3139
sortables = new Map<string, MdSortable>();
3240

@@ -74,6 +82,24 @@ export class MdSort {
7482
/** Event emitted when the user changes either the active sort or sort direction. */
7583
@Output() mdSortChange = new EventEmitter<Sort>();
7684

85+
constructor(private table: MdTable<any>) {}
86+
87+
ngAfterContentChecked() {
88+
// If the MdSort has been applied to an MdTable and its data source is an instance of
89+
// the MdTableDataSource, then provide this to the data source so it can respond to sort events.
90+
if (this.table && this.table.dataSource instanceof MdTableDataSource &&
91+
this.table.dataSource != this.tableDataSource) {
92+
this.tableDataSource = this.table.dataSource;
93+
this.table.dataSource.sort = this;
94+
}
95+
}
96+
97+
ngOnDestroy() {
98+
if (this.tableDataSource) {
99+
this.tableDataSource.sort = null;
100+
}
101+
}
102+
77103
/**
78104
* Register function to be used by the contained MdSortables. Adds the MdSortable to the
79105
* collection of MdSortables.

0 commit comments

Comments
 (0)