@@ -6,6 +6,10 @@ import {
6
6
ContentChildren ,
7
7
Directive ,
8
8
Input ,
9
+ IterableChangeRecord ,
10
+ IterableDiffer ,
11
+ IterableDiffers ,
12
+ NgIterable ,
9
13
QueryList ,
10
14
ViewChild ,
11
15
ViewContainerRef ,
@@ -38,7 +42,7 @@ export class HeaderRowPlaceholder {
38
42
}
39
43
40
44
/**
41
- * A data table that connects with a data source to retrieve data and renders
45
+ * A data table that connects with a data source to retrieve data of type T and renders
42
46
* a header row and data rows. Updates the rows when new data is provided by the data source.
43
47
*/
44
48
@Component ( {
@@ -54,12 +58,12 @@ export class HeaderRowPlaceholder {
54
58
encapsulation : ViewEncapsulation . None ,
55
59
changeDetection : ChangeDetectionStrategy . OnPush ,
56
60
} )
57
- export class CdkTable implements CollectionViewer {
61
+ export class CdkTable < T > implements CollectionViewer {
58
62
/**
59
63
* Provides a stream containing the latest data array to render. Influenced by the table's
60
64
* stream of view window (what rows are currently on screen).
61
65
*/
62
- @Input ( ) dataSource : DataSource < any > ;
66
+ @Input ( ) dataSource : DataSource < T > ;
63
67
64
68
// TODO(andrewseguin): Remove max value as the end index
65
69
// and instead calculate the view on init and scroll.
@@ -76,6 +80,9 @@ export class CdkTable implements CollectionViewer {
76
80
*/
77
81
private _columnDefinitionsByName = new Map < string , CdkColumnDef > ( ) ;
78
82
83
+ /** Differ used to find the changes in the data provided by the data source. */
84
+ private _dataDiffer : IterableDiffer < T > = null ;
85
+
79
86
// Placeholders within the table's template where the header and data rows will be inserted.
80
87
@ViewChild ( RowPlaceholder ) _rowPlaceholder : RowPlaceholder ;
81
88
@ViewChild ( HeaderRowPlaceholder ) _headerRowPlaceholder : HeaderRowPlaceholder ;
@@ -92,9 +99,14 @@ export class CdkTable implements CollectionViewer {
92
99
/** Set of templates that used as the data row containers. */
93
100
@ContentChildren ( CdkRowDef ) _rowDefinitions : QueryList < CdkRowDef > ;
94
101
95
- constructor ( private _changeDetectorRef : ChangeDetectorRef ) {
102
+ constructor ( private readonly _differs : IterableDiffers ,
103
+ private readonly _changeDetectorRef : ChangeDetectorRef ) {
96
104
console . warn ( 'The data table is still in active development ' +
97
105
'and should be considered unstable.' ) ;
106
+
107
+ // TODO(andrewseguin): Add trackby function input.
108
+ // Find and construct an iterable differ that can be used to find the diff in an array.
109
+ this . _dataDiffer = this . _differs . find ( [ ] ) . create ( ) ;
98
110
}
99
111
100
112
ngOnDestroy ( ) {
@@ -122,12 +134,8 @@ export class CdkTable implements CollectionViewer {
122
134
// TODO(andrewseguin): If the data source is not
123
135
// present after view init, connect it when it is defined.
124
136
// TODO(andrewseguin): Unsubscribe from this on destroy.
125
- this . dataSource . connect ( this ) . subscribe ( ( rowsData : any [ ] ) => {
126
- // TODO(andrewseguin): Add a differ that will check if the data has changed,
127
- // rather than re-rendering all rows
128
- this . _rowPlaceholder . viewContainer . clear ( ) ;
129
- rowsData . forEach ( rowData => this . insertRow ( rowData ) ) ;
130
- this . _changeDetectorRef . markForCheck ( ) ;
137
+ this . dataSource . connect ( this ) . subscribe ( ( rowsData : NgIterable < T > ) => {
138
+ this . renderRowChanges ( rowsData ) ;
131
139
} ) ;
132
140
}
133
141
@@ -146,11 +154,31 @@ export class CdkTable implements CollectionViewer {
146
154
CdkCellOutlet . mostRecentCellOutlet . context = { } ;
147
155
}
148
156
157
+ /** Check for changes made in the data and render each change (row added/removed/moved). */
158
+ renderRowChanges ( dataRows : NgIterable < T > ) {
159
+ const changes = this . _dataDiffer . diff ( dataRows ) ;
160
+ if ( ! changes ) { return ; }
161
+
162
+ changes . forEachOperation (
163
+ ( item : IterableChangeRecord < any > , adjustedPreviousIndex : number , currentIndex : number ) => {
164
+ if ( item . previousIndex == null ) {
165
+ this . insertRow ( dataRows [ currentIndex ] , currentIndex ) ;
166
+ } else if ( currentIndex == null ) {
167
+ this . _rowPlaceholder . viewContainer . remove ( adjustedPreviousIndex ) ;
168
+ } else {
169
+ const view = this . _rowPlaceholder . viewContainer . get ( adjustedPreviousIndex ) ;
170
+ this . _rowPlaceholder . viewContainer . move ( view , currentIndex ) ;
171
+ }
172
+ } ) ;
173
+
174
+ this . _changeDetectorRef . markForCheck ( ) ;
175
+ }
176
+
149
177
/**
150
178
* Create the embedded view for the data row template and place it in the correct index location
151
179
* within the data row view container.
152
180
*/
153
- insertRow ( rowData : any ) {
181
+ insertRow ( rowData : T , index : number ) {
154
182
// TODO(andrewseguin): Add when predicates to the row definitions
155
183
// to find the right template to used based on
156
184
// the data rather than choosing the first row definition.
@@ -161,7 +189,7 @@ export class CdkTable implements CollectionViewer {
161
189
162
190
// TODO(andrewseguin): add some code to enforce that exactly one
163
191
// CdkCellOutlet was instantiated as a result of `createEmbeddedView`.
164
- this . _rowPlaceholder . viewContainer . createEmbeddedView ( row . template , context ) ;
192
+ this . _rowPlaceholder . viewContainer . createEmbeddedView ( row . template , context , index ) ;
165
193
166
194
// Insert empty cells if there is no data to improve rendering time.
167
195
CdkCellOutlet . mostRecentCellOutlet . cells = rowData ? this . getCellTemplatesForRow ( row ) : [ ] ;
0 commit comments