-
Notifications
You must be signed in to change notification settings - Fork 6.8k
feat(table): enable multiple data rows #10964
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(table): enable multiple data rows #10964
Conversation
f3870e0
to
52834b2
Compare
For the index stuff: I think that, when in multiple-row mode, you should have separate |
src/cdk/table/table.ts
Outdated
@@ -98,6 +99,16 @@ export const CDK_TABLE_TEMPLATE = ` | |||
*/ | |||
abstract class RowViewRef<T> extends EmbeddedViewRef<CdkCellOutletRowContext<T>> { } | |||
|
|||
/** | |||
* Set of properties that represent a single rendered row. Acts as the row's identity for the | |||
* `IterableDiffer`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you expand this to explain more of the why this is necessary? Should also explicitly say something like "There will be one DataRow instance per ____"
We also should avoid making this part of the public API.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added more to the comment, not sure whether its too verbose.
src/cdk/table/table.ts
Outdated
* `CdkRowDef<T>` object. With the two keys, the cache points to a `DataRow<T>` object that | ||
* contains the pair. | ||
*/ | ||
private _cachedDataRowsMap = new Map<T, Map<CdkRowDef<T>, DataRow<T>>>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this use WeakMap
? It doesn't look like you need to iterate over it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately not. T
may be a primitive value which would be an invalid WeakMap
key.
src/cdk/table/table.ts
Outdated
* `CdkRowDef<T>` object. With the two keys, the cache points to a `DataRow<T>` object that | ||
* contains the pair. | ||
*/ | ||
private _cachedDataRowsMap = new Map<T, Map<CdkRowDef<T>, DataRow<T>>>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why can't this just be a map of T -> DataRow<T>
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Each T
may map into multiple DataRow<T>
objects since each object represents a pair of data and row template.
This setup allows us to uniquely identify these pairs so that they can be re-used if they show up again between renders. This is necessary for the IterableDiffer
to understand the correct operations, e.g. when a pairing is moved.
src/cdk/table/table.ts
Outdated
* defined in the table, or otherwise the default row which does not have a when predicate. | ||
*/ | ||
@Input() | ||
get enableRowMultiplex(): boolean { return this._enableRowMultiplex; } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, so I refreshed my memory about multiplexers, and they actually do the opposite of what we want here: they take multiple input signals and output one. The opposite of that is "demuxing", and I think enableRowDemux
is a bit too jargony for this API.
How about just multiTemplateRows
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps it should be multiTemplateDataRows
since I intend to allow multiple header and footer rows which would not depend on this input.
src/cdk/table/table.ts
Outdated
@@ -262,8 +306,9 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke | |||
this._applyNativeTableSections(); | |||
} | |||
|
|||
// TODO(andrewseguin): Setup a listener for scrolling, emit the calculated view to viewChange | |||
this._dataDiffer = this._differs.find([]).create(this._trackByFn); | |||
this._dataDiffer = this._differs.find([]).create((_i: number, dataRow: DataRow<T>) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add comment explaining what is happening here? It's not quite clear to me
src/cdk/table/table.ts
Outdated
* row definitions. If the previous list already contained a particular pair, it should be reused | ||
* so that the differ equates their references. | ||
*/ | ||
private _getDataRows(): DataRow<T>[] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having both _getDataRows
and _getDataRowsForData
is confusing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, struggled coming up with a better name for these. One returns the list of all the DataRow
objects to render, while one returns the list of DataRow
objects for a particular data object.
Any ideas?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think _getAllRenderRows
and _getRenderRowsForData
would be good
52834b2
to
9fa7f08
Compare
Making the index and naming changes; will add |
Ready for review |
src/cdk/table/row.ts
Outdated
/** Index location of the rendered row that this cell is located within. */ | ||
renderIndex?: number; | ||
|
||
/** Index of the data object in the provided data array. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove index
?
src/cdk/table/table.spec.ts
Outdated
dataSource: FakeDataSource = new FakeDataSource(); | ||
columnsToRender = ['column_a', 'column_b', 'column_c']; | ||
isIndex1Columns = ['index1Column']; | ||
hasC3Columns = ['c3Column']; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These names are pretty cryptic
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed to columnsForIsIndex1Row
and columnsForHasC3Row
. Not totally sure thats better :)
src/cdk/table/table.ts
Outdated
*/ | ||
export interface RenderRow<T> { | ||
data: T; | ||
index: number; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dataIndex
or renderIndex
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed to dataIndex
src/cdk/table/table.ts
Outdated
get multiTemplateRows(): boolean { return this._multiTemplateRows; } | ||
set multiTemplateRows(v: boolean) { | ||
this._multiTemplateRows = coerceBooleanProperty(v); | ||
if (!!this._rowOutlet.viewContainer.length) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
!!
is unnecessary in conditionals
src/cdk/table/table.ts
Outdated
// respective `RenderRow` object which is the pair of `data` and `CdkRowDef`. | ||
for (let i = 0; i < this._data.length; i++) { | ||
let data = this._data[i]; | ||
const dataRowsForData = this._getRenderRowsForData(data, i, prevCachedRenderRows.get(data)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
renderRowsForData
src/cdk/table/table.ts
Outdated
private _insertRow(rowData: T, index: number) { | ||
const rowDef = this._getRowDef(rowData, index); | ||
const context: CdkCellOutletRowContext<T> = {$implicit: rowData}; | ||
private _insertRow(dataRow: RenderRow<T>, index: number) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
renderRow
src/cdk/table/row.ts
Outdated
odd?: boolean; | ||
} | ||
|
||
/** Context provided to the row cells when `multiTemplateDataRows` is true */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Expand to say something like
This is the same as CdkCellOutletRowContext, except that the single `index` is
replaced with `renderIndex` and `dataIndex`.
* @docs-private | ||
*/ | ||
export interface RowContext<T> | ||
extends CdkCellOutletMultiRowContext<T>, CdkCellOutletRowContext<T> { } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't want to extend both, you want a union type
export type RowContext = CdkCellOutletMultiRowContext | CdkCellOutletRowContext;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tried that but typescript didn't like it. Perhaps I'm doing something incorrectly?
src/cdk/table/table.ts
Outdated
@@ -620,4 +755,11 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke | |||
this._elementRef.nativeElement.appendChild(element); | |||
} | |||
} | |||
|
|||
/** Forces a re-render of the data rows. */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Expand this comment with information about when it's appropriate to call this function?
src/lib/table/table.spec.ts
Outdated
@@ -56,6 +56,23 @@ describe('MatTable', () => { | |||
['Footer A', 'Footer B', 'Footer C'], | |||
]); | |||
}); | |||
|
|||
it('should create a table with row multiplex', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
multiplex
Thanks, should have done a better job of renaming dataRow to renderRow. Forgot to find/replace that lowercase one version |
Note to caretaker, will require BUILD change to add See change 194450307 for patch. |
|
||
// For each data object, get the list of rows that should be rendered, represented by the | ||
// respective `RenderRow` object which is the pair of `data` and `CdkRowDef`. | ||
for (let i = 0; i < this._data.length; i++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to check/handle if empty
Removing Writing up some tests and will add the label and a comment when ready again. |
d64a8ff
to
75ced9a
Compare
75ced9a
to
fe56e56
Compare
Ready again for review. Added docs on the table's implementation process for rendering rows. Renamed the input to |
src/cdk/table/table.ts
Outdated
} | ||
|
||
/** | ||
* Returns a list of `RenderRow<T>` for the provided data object and any `CdkRowDef` objects that |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Prefer "Gets" to "Returns` in descriptions
src/cdk/table/table.ts
Outdated
* 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[0]; } | ||
_getRowDefs(data: T, i: number): CdkRowDef<T>[] { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Still make this more specific than i
?
src/cdk/table/table.spec.ts
Outdated
]); | ||
}); | ||
|
||
it('should have the correct data and row indicies when data is homogeneous', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Homogeneous" doesn't really communicate the same thing as "Data contains multiple occurrences of the same object instance"
373bfa9
to
075d680
Compare
Thanks - made the changes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
This reverts commit 0e466ee.
This issue has been automatically locked due to inactivity. Read more about our automatic conversation locking policy. This action has been performed automatically by a bot. |
Introduces a new table input
multiTemplateDataRows
which allows the table to have multiple data rows per data object.Old paradigm
Each data object passed to the table is rendered as a data row. Since the mapping is 1:1, the data array itself can be used with the
IterableDiffer
to determine which rows need to change.New paradigm
Each data object passed to the table may be rendered by one or more rows. The
IterableDiffer
cannot use the data array itself anymore since it will not correctly understand add/remove/move operations in the rows.Instead, the
IterableDiffer
will be diffed with aDataRow
object that represents a pair containing a data object and row template. When the data changes, the list ofDataRow
objects will be constructed and diffed to get the list of changes.Caveats for discussion
index
value that represented both the row index and data object index (they were the same. Now, thatindex
value represents the index of the data. To get the row index, there is an additional context property calledrowIndex
.Should it be swapped such that
index
is the row index and the data index can be stored indataIndex
?trackBy
function receives an index value. Previously the index matched both the data and row. Now, its explicitly the data index.Should
trackBy
receive the row index?