mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
Generated
+2
-2
@@ -4489,7 +4489,7 @@
|
||||
},
|
||||
"event-stream": {
|
||||
"version": "3.3.4",
|
||||
"resolved": "http://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz",
|
||||
"resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz",
|
||||
"integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
@@ -13291,7 +13291,7 @@
|
||||
},
|
||||
"onetime": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
|
||||
"resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
|
||||
"integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
|
||||
"dev": true
|
||||
},
|
||||
|
||||
@@ -9,7 +9,6 @@ ng_module(
|
||||
module_name = "@angular/cdk/table",
|
||||
deps = [
|
||||
"//src/cdk/collections",
|
||||
"//src/cdk/coercion",
|
||||
"@rxjs",
|
||||
],
|
||||
tsconfig = "//src/cdk:tsconfig-build.json",
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
# Rendering Data Rows
|
||||
|
||||
The table's primary responsibility is to render rows of cells. The types of rows that may be rendered are header,
|
||||
footer, and data rows. This document focuses on how the table tries to efficienctly render the data rows.
|
||||
|
||||
## Background
|
||||
|
||||
Each table's template is defined as a set of row and column templates. The row template defines the template that should
|
||||
be rendered for a header, footer, or data row. The column templates include the cell templates that will be inserted
|
||||
into each rendered row.
|
||||
|
||||
Each data object may be rendered with one or more row templates. When new data in provided to the table, the table
|
||||
determines which rows need to be rendered. In order to be efficient, the table attempts to understand how the new list
|
||||
of rendered rows differs from the previous list of rendered rows so that it can re-use the current list of rendered rows
|
||||
if possible.
|
||||
|
||||
## Rendering
|
||||
|
||||
Each time data is provided, the table needs to create the list of rows that will be rendered and keep track of which
|
||||
data object will be provided as context for each row. For each item in the list, this pair is combined into an object
|
||||
that uses the `RenderRow` interface. The interface also helps keep track of the data object's index in the provided
|
||||
data array input.
|
||||
|
||||
```ts
|
||||
export interface RenderRow<T> {
|
||||
data: T;
|
||||
dataIndex: number;
|
||||
rowDef: CdkRowDef<T>;
|
||||
}
|
||||
```
|
||||
|
||||
When possible, `RenderRow` objects are re-used from the previous rendering. That is, if a particular data object and row
|
||||
template pairing was previously rendered, it should be used for the new list as well. This makes sure that the
|
||||
differ can use check-by-reference logic to find the changes between two lists. Note that if a `RenderRow` object is
|
||||
reused, it should be updated with the correct data index, in case it has moved since last used.
|
||||
|
||||
Once the list of `RenderRow` objects has been created, it should be compared to the previous list of `RenderRow`
|
||||
objects to find the difference in terms of inserts/deletions/moves. This is trivially done using the `IterableDiffer`
|
||||
logic provided by Angular Core.
|
||||
|
||||
Finally, the table uses the list of operations and manipulates the rows through add/remove/move operations.
|
||||
|
||||
## Caching `RenderRow` objects
|
||||
|
||||
Each `RenderRow` should be cached such that it is a constant-time lookup and retrieval based on the data object and
|
||||
row template pairing.
|
||||
|
||||
In order to achieve this, the cache is built as a map of maps where the key of the outer map is the data object and
|
||||
the key of the inner map is the row template. The value of the inner map should be an array of the matching cached
|
||||
`RenderRow` objects that were previously rendered.
|
||||
|
||||
+2
-33
@@ -130,12 +130,12 @@ export class CdkRowDef<T> extends BaseRowDef {
|
||||
}
|
||||
}
|
||||
|
||||
/** Context provided to the row cells when `multiTemplateDataRows` is false */
|
||||
/** Context provided to the row cells */
|
||||
export interface CdkCellOutletRowContext<T> {
|
||||
/** Data for the row that this cell is located within. */
|
||||
$implicit?: T;
|
||||
|
||||
/** Index of the data object in the provided data array. */
|
||||
/** Index location of the row that this cell is located within. */
|
||||
index?: number;
|
||||
|
||||
/** Length of the number of total rows. */
|
||||
@@ -154,37 +154,6 @@ export interface CdkCellOutletRowContext<T> {
|
||||
odd?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Context provided to the row cells when `multiTemplateDataRows` is true. This context is the same
|
||||
* as CdkCellOutletRowContext except that the single `index` value is replaced by `dataIndex` and
|
||||
* `renderIndex`.
|
||||
*/
|
||||
export interface CdkCellOutletMultiRowContext<T> {
|
||||
/** Data for the row that this cell is located within. */
|
||||
$implicit?: T;
|
||||
|
||||
/** Index of the data object in the provided data array. */
|
||||
dataIndex?: number;
|
||||
|
||||
/** Index location of the rendered row that this cell is located within. */
|
||||
renderIndex?: number;
|
||||
|
||||
/** Length of the number of total rows. */
|
||||
count?: number;
|
||||
|
||||
/** True if this cell is contained in the first row. */
|
||||
first?: boolean;
|
||||
|
||||
/** True if this cell is contained in the last row. */
|
||||
last?: boolean;
|
||||
|
||||
/** True if this cell is contained in a row with an even-numbered index. */
|
||||
even?: boolean;
|
||||
|
||||
/** True if this cell is contained in a row with an odd-numbered index. */
|
||||
odd?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Outlet for rendering cells inside of a row or header row.
|
||||
* @docs-private
|
||||
|
||||
@@ -35,9 +35,8 @@ export function getTableMultipleDefaultRowDefsError() {
|
||||
* Returns an error to be thrown when there are no matching row defs for a particular set of data.
|
||||
* @docs-private
|
||||
*/
|
||||
export function getTableMissingMatchingRowDefError(data: any) {
|
||||
return Error(`Could not find a matching row definition for the` +
|
||||
`provided row data: ${JSON.stringify(data)}`);
|
||||
export function getTableMissingMatchingRowDefError() {
|
||||
return Error(`Could not find a matching row definition for the provided row data.`);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+42
-198
@@ -10,7 +10,7 @@ import {
|
||||
import {ComponentFixture, TestBed, fakeAsync, flush} from '@angular/core/testing';
|
||||
import {CdkTable} from './table';
|
||||
import {CollectionViewer, DataSource} from '@angular/cdk/collections';
|
||||
import {combineLatest, BehaviorSubject, Observable, of as observableOf} from 'rxjs';
|
||||
import {combineLatest, BehaviorSubject, Observable} from 'rxjs';
|
||||
import {map} from 'rxjs/operators';
|
||||
import {CdkTableModule} from './index';
|
||||
import {
|
||||
@@ -133,70 +133,37 @@ describe('CdkTable', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('should correctly use the differ to add/remove/move rows', () => {
|
||||
function addInitialIndexAttribute() {
|
||||
// Each row receives an attribute 'initialIndex' the element's original place
|
||||
getRows(tableElement).forEach((row: Element, index: number) => {
|
||||
row.setAttribute('initialIndex', index.toString());
|
||||
});
|
||||
|
||||
// Prove that the attributes match their indicies
|
||||
const initialRows = getRows(tableElement);
|
||||
expect(initialRows[0].getAttribute('initialIndex')).toBe('0');
|
||||
expect(initialRows[1].getAttribute('initialIndex')).toBe('1');
|
||||
expect(initialRows[2].getAttribute('initialIndex')).toBe('2');
|
||||
}
|
||||
|
||||
it('when the data is heterogeneous', () => {
|
||||
addInitialIndexAttribute();
|
||||
|
||||
// Swap first and second data in data array
|
||||
const copiedData = component.dataSource!.data.slice();
|
||||
const temp = copiedData[0];
|
||||
copiedData[0] = copiedData[1];
|
||||
copiedData[1] = temp;
|
||||
|
||||
// Remove the third element
|
||||
copiedData.splice(2, 1);
|
||||
|
||||
// Add new data
|
||||
component.dataSource!.data = copiedData;
|
||||
component.dataSource!.addData();
|
||||
|
||||
// Expect that the first and second rows were swapped and that the last row is new
|
||||
const changedRows = getRows(tableElement);
|
||||
expect(changedRows.length).toBe(3);
|
||||
expect(changedRows[0].getAttribute('initialIndex')).toBe('1');
|
||||
expect(changedRows[1].getAttribute('initialIndex')).toBe('0');
|
||||
expect(changedRows[2].getAttribute('initialIndex')).toBe(null);
|
||||
it('should use differ to add/remove/move rows', () => {
|
||||
// Each row receives an attribute 'initialIndex' the element's original place
|
||||
getRows(tableElement).forEach((row: Element, index: number) => {
|
||||
row.setAttribute('initialIndex', index.toString());
|
||||
});
|
||||
|
||||
it('when the data contains multiple occurrences of the same object instance', () => {
|
||||
const obj = {value: true};
|
||||
component.dataSource!.data = [obj, obj, obj];
|
||||
addInitialIndexAttribute();
|
||||
// Prove that the attributes match their indicies
|
||||
const initialRows = getRows(tableElement);
|
||||
expect(initialRows[0].getAttribute('initialIndex')).toBe('0');
|
||||
expect(initialRows[1].getAttribute('initialIndex')).toBe('1');
|
||||
expect(initialRows[2].getAttribute('initialIndex')).toBe('2');
|
||||
|
||||
const copiedData = component.dataSource!.data.slice();
|
||||
// Swap first and second data in data array
|
||||
const copiedData = component.dataSource!.data.slice();
|
||||
const temp = copiedData[0];
|
||||
copiedData[0] = copiedData[1];
|
||||
copiedData[1] = temp;
|
||||
|
||||
// Remove the third element and add a new different obj in the beginning.
|
||||
copiedData.splice(2, 1);
|
||||
copiedData.unshift({value: false});
|
||||
// Remove the third element
|
||||
copiedData.splice(2, 1);
|
||||
|
||||
// Add new data
|
||||
component.dataSource!.data = copiedData;
|
||||
// Add new data
|
||||
component.dataSource!.data = copiedData;
|
||||
component.dataSource!.addData();
|
||||
|
||||
// Expect that two of the three rows still have an initial index. Not as concerned about
|
||||
// the order they are in, but more important that there was no unnecessary removes/inserts.
|
||||
const changedRows = getRows(tableElement);
|
||||
expect(changedRows.length).toBe(3);
|
||||
let numInitialRows = 0;
|
||||
changedRows.forEach(row => {
|
||||
if (row.getAttribute('initialIndex') !== null) {
|
||||
numInitialRows++;
|
||||
}
|
||||
});
|
||||
expect(numInitialRows).toBe(2);
|
||||
});
|
||||
// Expect that the first and second rows were swapped and that the last row is new
|
||||
const changedRows = getRows(tableElement);
|
||||
expect(changedRows.length).toBe(3);
|
||||
expect(changedRows[0].getAttribute('initialIndex')).toBe('1');
|
||||
expect(changedRows[1].getAttribute('initialIndex')).toBe('0');
|
||||
expect(changedRows[2].getAttribute('initialIndex')).toBe(null);
|
||||
});
|
||||
|
||||
it('should clear the row view containers on destroy', () => {
|
||||
@@ -271,13 +238,6 @@ describe('CdkTable', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should render no rows when the data is null', fakeAsync(() => {
|
||||
setupTableTestApp(NullDataCdkTableApp);
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(getRows(tableElement).length).toBe(0);
|
||||
}));
|
||||
|
||||
describe('with different data inputs other than data source', () => {
|
||||
let baseData: TestData[] = [
|
||||
{a: 'a_1', b: 'b_1', c: 'c_1'},
|
||||
@@ -287,6 +247,7 @@ describe('CdkTable', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
setupTableTestApp(CdkTableWithDifferentDataInputsApp);
|
||||
component = fixture.componentInstance;
|
||||
});
|
||||
|
||||
it('should render with data array input', () => {
|
||||
@@ -562,98 +523,24 @@ describe('CdkTable', () => {
|
||||
});
|
||||
|
||||
it('should error if there is row data that does not have a matching row template',
|
||||
fakeAsync(() => {
|
||||
const whenRowWithoutDefaultFixture = createComponent(WhenRowWithoutDefaultCdkTableApp);
|
||||
const data = whenRowWithoutDefaultFixture.componentInstance.dataSource.data;
|
||||
expect(() => {
|
||||
try {
|
||||
whenRowWithoutDefaultFixture.detectChanges();
|
||||
flush();
|
||||
} catch {
|
||||
flush();
|
||||
}
|
||||
}).toThrowError(getTableMissingMatchingRowDefError(data[0]).message);
|
||||
}));
|
||||
fakeAsync(() => {
|
||||
expect(() => {
|
||||
try {
|
||||
createComponent(WhenRowWithoutDefaultCdkTableApp).detectChanges();
|
||||
flush();
|
||||
} catch {
|
||||
flush();
|
||||
}
|
||||
}).toThrowError(getTableMissingMatchingRowDefError().message);
|
||||
}));
|
||||
|
||||
it('should fail when multiple rows match data without multiTemplateDataRows', fakeAsync(() => {
|
||||
it('should error if there are multiple rows that do not have a when function', fakeAsync(() => {
|
||||
let whenFixture = createComponent(WhenRowMultipleDefaultsCdkTableApp);
|
||||
expect(() => {
|
||||
whenFixture.detectChanges();
|
||||
flush();
|
||||
}).toThrowError(getTableMultipleDefaultRowDefsError().message);
|
||||
}));
|
||||
|
||||
describe('with multiTemplateDataRows', () => {
|
||||
it('should be able to render multiple rows per data object', () => {
|
||||
setupTableTestApp(WhenRowCdkTableApp);
|
||||
component.multiTemplateDataRows = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
const data = component.dataSource.data;
|
||||
expectTableToMatchContent(tableElement, [
|
||||
['Column A', 'Column B', 'Column C'],
|
||||
[data[0].a, data[0].b, data[0].c],
|
||||
[data[1].a, data[1].b, data[1].c],
|
||||
['index_1_special_row'],
|
||||
[data[2].a, data[2].b, data[2].c],
|
||||
['c3_special_row'],
|
||||
[data[3].a, data[3].b, data[3].c],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should have the correct data and row indicies', () => {
|
||||
setupTableTestApp(WhenRowCdkTableApp);
|
||||
component.multiTemplateDataRows = true;
|
||||
component.showIndexColumns();
|
||||
fixture.detectChanges();
|
||||
|
||||
expectTableToMatchContent(tableElement, [
|
||||
['Index', 'Data Index', 'Render Index'],
|
||||
['', '0', '0'],
|
||||
['', '1', '1'],
|
||||
['', '1', '2'],
|
||||
['', '2', '3'],
|
||||
['', '2', '4'],
|
||||
['', '3', '5'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should have the correct data and row indicies when data contains multiple instances of ' +
|
||||
'the same object instance', () => {
|
||||
setupTableTestApp(WhenRowCdkTableApp);
|
||||
component.multiTemplateDataRows = true;
|
||||
component.showIndexColumns();
|
||||
|
||||
const obj = {value: true};
|
||||
component.dataSource.data = [obj, obj, obj, obj];
|
||||
fixture.detectChanges();
|
||||
|
||||
expectTableToMatchContent(tableElement, [
|
||||
['Index', 'Data Index', 'Render Index'],
|
||||
['', '0', '0'],
|
||||
['', '1', '1'],
|
||||
['', '1', '2'],
|
||||
['', '2', '3'],
|
||||
['', '3', '4'],
|
||||
]);
|
||||
|
||||
// Push unique data on the front and add another obj to the array
|
||||
component.dataSource.data = [{value: false}, obj, obj, obj, obj, obj];
|
||||
fixture.detectChanges();
|
||||
|
||||
expectTableToMatchContent(tableElement, [
|
||||
['Index', 'Data Index', 'Render Index'],
|
||||
['', '0', '0'],
|
||||
['', '1', '1'],
|
||||
['', '1', '2'],
|
||||
['', '2', '3'],
|
||||
['', '3', '4'],
|
||||
['', '4', '5'],
|
||||
['', '5', '6'],
|
||||
]);
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('with trackBy', () => {
|
||||
@@ -1034,27 +921,9 @@ class BooleanRowCdkTableApp {
|
||||
dataSource = new BooleanDataSource();
|
||||
}
|
||||
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<cdk-table [dataSource]="dataSource">
|
||||
<ng-container cdkColumnDef="column_a">
|
||||
<cdk-header-cell *cdkHeaderCellDef></cdk-header-cell>
|
||||
<cdk-cell *cdkCellDef="let data"> {{data}} </cdk-cell>
|
||||
</ng-container>
|
||||
|
||||
<cdk-header-row *cdkHeaderRowDef="['column_a']"></cdk-header-row>
|
||||
<cdk-row *cdkRowDef="let row; columns: ['column_a']"></cdk-row>
|
||||
</cdk-table>
|
||||
`
|
||||
})
|
||||
class NullDataCdkTableApp {
|
||||
dataSource = observableOf(null);
|
||||
}
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<cdk-table [dataSource]="dataSource" [multiTemplateDataRows]="multiTemplateDataRows">
|
||||
<ng-container cdkColumnDef="column_a">
|
||||
<cdk-header-cell *cdkHeaderCellDef> Column A</cdk-header-cell>
|
||||
<cdk-cell *cdkCellDef="let row"> {{row.a}}</cdk-cell>
|
||||
@@ -1072,55 +941,30 @@ class NullDataCdkTableApp {
|
||||
|
||||
<ng-container cdkColumnDef="index1Column">
|
||||
<cdk-header-cell *cdkHeaderCellDef> Column C</cdk-header-cell>
|
||||
<cdk-cell *cdkCellDef="let row"> index_1_special_row</cdk-cell>
|
||||
<cdk-cell *cdkCellDef="let row"> index_1_special_row </cdk-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container cdkColumnDef="c3Column">
|
||||
<cdk-header-cell *cdkHeaderCellDef> Column C</cdk-header-cell>
|
||||
<cdk-cell *cdkCellDef="let row"> c3_special_row</cdk-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container cdkColumnDef="index">
|
||||
<cdk-header-cell *cdkHeaderCellDef> Index</cdk-header-cell>
|
||||
<cdk-cell *cdkCellDef="let row; let index = index"> {{index}}</cdk-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container cdkColumnDef="dataIndex">
|
||||
<cdk-header-cell *cdkHeaderCellDef> Data Index</cdk-header-cell>
|
||||
<cdk-cell *cdkCellDef="let row; let dataIndex = dataIndex"> {{dataIndex}}</cdk-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container cdkColumnDef="renderIndex">
|
||||
<cdk-header-cell *cdkHeaderCellDef> Render Index</cdk-header-cell>
|
||||
<cdk-cell *cdkCellDef="let row; let renderIndex = renderIndex"> {{renderIndex}}</cdk-cell>
|
||||
<cdk-cell *cdkCellDef="let row"> c3_special_row </cdk-cell>
|
||||
</ng-container>
|
||||
|
||||
<cdk-header-row *cdkHeaderRowDef="columnsToRender"></cdk-header-row>
|
||||
<cdk-row *cdkRowDef="let row; columns: columnsToRender"></cdk-row>
|
||||
<cdk-row *cdkRowDef="let row; columns: columnsForIsIndex1Row; when: isIndex1"></cdk-row>
|
||||
<cdk-row *cdkRowDef="let row; columns: columnsForHasC3Row; when: hasC3"></cdk-row>
|
||||
<cdk-row *cdkRowDef="let row; columns: ['index1Column']; when: isIndex1"></cdk-row>
|
||||
<cdk-row *cdkRowDef="let row; columns: ['c3Column']; when: hasC3"></cdk-row>
|
||||
</cdk-table>
|
||||
`
|
||||
})
|
||||
class WhenRowCdkTableApp {
|
||||
multiTemplateDataRows = false;
|
||||
dataSource: FakeDataSource = new FakeDataSource();
|
||||
columnsToRender = ['column_a', 'column_b', 'column_c'];
|
||||
columnsForIsIndex1Row = ['index1Column'];
|
||||
columnsForHasC3Row = ['c3Column'];
|
||||
isIndex1 = (index: number, _rowData: TestData) => index == 1;
|
||||
hasC3 = (_index: number, rowData: TestData) => rowData.c == 'c_3';
|
||||
|
||||
constructor() { this.dataSource.addData(); }
|
||||
|
||||
@ViewChild(CdkTable) table: CdkTable<TestData>;
|
||||
|
||||
showIndexColumns() {
|
||||
const indexColumns = ['index', 'dataIndex', 'renderIndex'];
|
||||
this.columnsToRender = indexColumns;
|
||||
this.columnsForIsIndex1Row = indexColumns;
|
||||
this.columnsForHasC3Row = indexColumns;
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
|
||||
+38
-196
@@ -34,7 +34,6 @@ import {CollectionViewer, DataSource} from '@angular/cdk/collections';
|
||||
import {
|
||||
BaseRowDef,
|
||||
CdkCellOutlet,
|
||||
CdkCellOutletMultiRowContext,
|
||||
CdkCellOutletRowContext,
|
||||
CdkFooterRowDef,
|
||||
CdkHeaderRowDef,
|
||||
@@ -51,7 +50,6 @@ import {
|
||||
getTableUnknownColumnError,
|
||||
getTableUnknownDataSourceError
|
||||
} from './table-errors';
|
||||
import {coerceBooleanProperty} from '@angular/cdk/coercion';
|
||||
|
||||
/** Interface used to provide an outlet for rows to be inserted into. */
|
||||
export interface RowOutlet {
|
||||
@@ -95,37 +93,11 @@ export const CDK_TABLE_TEMPLATE = `
|
||||
<ng-container rowOutlet></ng-container>
|
||||
<ng-container footerRowOutlet></ng-container>`;
|
||||
|
||||
/**
|
||||
* Interface used to conveniently type the possible context interfaces for the render row.
|
||||
* @docs-private
|
||||
*/
|
||||
export interface RowContext<T>
|
||||
extends CdkCellOutletMultiRowContext<T>, CdkCellOutletRowContext<T> { }
|
||||
|
||||
/**
|
||||
* Class used to conveniently type the embedded view ref for rows with a context.
|
||||
* @docs-private
|
||||
*/
|
||||
abstract class RowViewRef<T> extends EmbeddedViewRef<RowContext<T>> { }
|
||||
|
||||
/**
|
||||
* Set of properties that represents the identity of a single rendered row.
|
||||
*
|
||||
* When the table needs to determine the list of rows to render, it will do so by iterating through
|
||||
* each data object and evaluating its list of row templates to display (when multiTemplateDataRows
|
||||
* is false, there is only one template per data object). For each pair of data object and row
|
||||
* template, a `RenderRow` is added to the list of rows to render. If the data object and row
|
||||
* template pair has already been rendered, the previously used `RenderRow` is added; else a new
|
||||
* `RenderRow` is * created. Once the list is complete and all data objects have been itereated
|
||||
* through, a diff is performed to determine the changes that need to be made to the rendered rows.
|
||||
*
|
||||
* @docs-private
|
||||
*/
|
||||
export interface RenderRow<T> {
|
||||
data: T;
|
||||
dataIndex: number;
|
||||
rowDef: CdkRowDef<T>;
|
||||
}
|
||||
abstract class RowViewRef<T> extends EmbeddedViewRef<CdkCellOutletRowContext<T>> { }
|
||||
|
||||
/**
|
||||
* A data table that can render a header row, data rows, and a footer row.
|
||||
@@ -151,9 +123,6 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke
|
||||
/** Latest data provided by the data source. */
|
||||
private _data: T[];
|
||||
|
||||
/** List of the rendered rows as identified by their `RenderRow` object. */
|
||||
private _renderRows: RenderRow<T>[];
|
||||
|
||||
/** Subscription that listens for the data provided by the data source. */
|
||||
private _renderChangeSubscription: Subscription | null;
|
||||
|
||||
@@ -171,7 +140,7 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke
|
||||
private _rowDefs: CdkRowDef<T>[];
|
||||
|
||||
/** Differ used to find the changes in the data provided by the data source. */
|
||||
private _dataDiffer: IterableDiffer<RenderRow<T>>;
|
||||
private _dataDiffer: IterableDiffer<T>;
|
||||
|
||||
/** Stores the row definition that does not have a when predicate. */
|
||||
private _defaultRowDef: CdkRowDef<T> | null;
|
||||
@@ -194,21 +163,6 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke
|
||||
*/
|
||||
private _footerRowDefChanged = false;
|
||||
|
||||
/**
|
||||
* Cache of the latest rendered `RenderRow` objects as a map for easy retrieval when constructing
|
||||
* a new list of `RenderRow` objects for rendering rows. Since the new list is constructed with
|
||||
* the cached `RenderRow` objects when possible, the row identity is preserved when the data
|
||||
* and row template matches, which allows the `IterableDiffer` to check rows by reference
|
||||
* and understand which rows are added/moved/removed.
|
||||
*
|
||||
* Implemented as a map of maps where the first key is the `data: T` object and the second is the
|
||||
* `CdkRowDef<T>` object. With the two keys, the cache points to a `RenderRow<T>` object that
|
||||
* contains an array of created pairs. The array is necessary to handle cases where the data
|
||||
* array contains multiple duplicate data objects and each instantiated `RenderRow` must be
|
||||
* stored.
|
||||
*/
|
||||
private _cachedRenderRowsMap = new Map<T, WeakMap<CdkRowDef<T>, RenderRow<T>[]>>();
|
||||
|
||||
/**
|
||||
* Tracking function that will be used to check the differences in data changes. Used similarly
|
||||
* to `ngFor` `trackBy` function. Optimize row operations by identifying a row based on its data
|
||||
@@ -256,22 +210,6 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke
|
||||
}
|
||||
private _dataSource: DataSource<T> | Observable<T[]> | T[] | T[];
|
||||
|
||||
/**
|
||||
* Whether to allow multiple rows per data object by evaluating which rows evaluate their 'when'
|
||||
* predicate to true. If `multiTemplateDataRows` is false, which is the default value, then each
|
||||
* dataobject will render the first row that evaluates its when predicate to true, in the order
|
||||
* defined in the table, or otherwise the default row which does not have a when predicate.
|
||||
*/
|
||||
@Input()
|
||||
get multiTemplateDataRows(): boolean { return this._multiTemplateDataRows; }
|
||||
set multiTemplateDataRows(v: boolean) {
|
||||
this._multiTemplateDataRows = coerceBooleanProperty(v);
|
||||
if (this._rowOutlet.viewContainer.length) {
|
||||
this._forceRenderRows();
|
||||
}
|
||||
}
|
||||
_multiTemplateDataRows: boolean = false;
|
||||
|
||||
// TODO(andrewseguin): Remove max value as the end index
|
||||
// and instead calculate the view on init and scroll.
|
||||
/**
|
||||
@@ -325,12 +263,8 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke
|
||||
this._applyNativeTableSections();
|
||||
}
|
||||
|
||||
// Set up the trackBy function so that it uses the `RenderRow` as its identity by default. If
|
||||
// the user has provided a custom trackBy, return the result of that function as evaluated
|
||||
// with the values of the `RenderRow`'s data and index.
|
||||
this._dataDiffer = this._differs.find([]).create((_i: number, dataRow: RenderRow<T>) => {
|
||||
return this.trackBy ? this.trackBy(dataRow.dataIndex, dataRow.data) : dataRow;
|
||||
});
|
||||
// TODO(andrewseguin): Setup a listener for scrolling, emit the calculated view to viewChange
|
||||
this._dataDiffer = this._differs.find([]).create(this._trackByFn);
|
||||
|
||||
// If the table has header or footer row definitions defined as part of its content, mark that
|
||||
// there is a change so that the content check will render the row.
|
||||
@@ -374,9 +308,6 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke
|
||||
this._rowOutlet.viewContainer.clear();
|
||||
this._headerRowOutlet.viewContainer.clear();
|
||||
this._footerRowOutlet.viewContainer.clear();
|
||||
|
||||
this._cachedRenderRowsMap.clear();
|
||||
|
||||
this._onDestroy.next();
|
||||
this._onDestroy.complete();
|
||||
|
||||
@@ -396,19 +327,18 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke
|
||||
* an array, this function will need to be called to render any changes.
|
||||
*/
|
||||
renderRows() {
|
||||
this._renderRows = this._getAllRenderRows();
|
||||
const changes = this._dataDiffer.diff(this._renderRows);
|
||||
const changes = this._dataDiffer.diff(this._data);
|
||||
if (!changes) { return; }
|
||||
|
||||
const viewContainer = this._rowOutlet.viewContainer;
|
||||
changes.forEachOperation(
|
||||
(record: IterableChangeRecord<RenderRow<T>>, prevIndex: number, currentIndex: number) => {
|
||||
(record: IterableChangeRecord<T>, adjustedPreviousIndex: number, currentIndex: number) => {
|
||||
if (record.previousIndex == null) {
|
||||
this._insertRow(record.item, currentIndex);
|
||||
} else if (currentIndex == null) {
|
||||
viewContainer.remove(prevIndex);
|
||||
viewContainer.remove(adjustedPreviousIndex);
|
||||
} else {
|
||||
const view = <RowViewRef<T>>viewContainer.get(prevIndex);
|
||||
const view = <RowViewRef<T>>viewContainer.get(adjustedPreviousIndex);
|
||||
viewContainer.move(view!, currentIndex);
|
||||
}
|
||||
});
|
||||
@@ -418,9 +348,9 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke
|
||||
|
||||
// Update rows that did not get added/removed/moved but may have had their identity changed,
|
||||
// e.g. if trackBy matched data on some property but the actual data reference changed.
|
||||
changes.forEachIdentityChange((record: IterableChangeRecord<RenderRow<T>>) => {
|
||||
changes.forEachIdentityChange((record: IterableChangeRecord<T>) => {
|
||||
const rowView = <RowViewRef<T>>viewContainer.get(record.currentIndex!);
|
||||
rowView.context.$implicit = record.item.data;
|
||||
rowView.context.$implicit = record.item;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -464,66 +394,6 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke
|
||||
this._customRowDefs.delete(rowDef);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of RenderRow objects to render according to the current list of data and defined
|
||||
* row definitions. If the previous list already contained a particular pair, it should be reused
|
||||
* so that the differ equates their references.
|
||||
*/
|
||||
private _getAllRenderRows(): RenderRow<T>[] {
|
||||
const renderRows: RenderRow<T>[] = [];
|
||||
|
||||
// Store the cache and create a new one. Any re-used RenderRow objects will be moved into the
|
||||
// new cache while unused ones can be picked up by garbage collection.
|
||||
const prevCachedRenderRows = this._cachedRenderRowsMap;
|
||||
this._cachedRenderRowsMap = new Map();
|
||||
|
||||
// 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++) {
|
||||
let data = this._data[i];
|
||||
const renderRowsForData = this._getRenderRowsForData(data, i, prevCachedRenderRows.get(data));
|
||||
|
||||
if (!this._cachedRenderRowsMap.has(data)) {
|
||||
this._cachedRenderRowsMap.set(data, new WeakMap());
|
||||
}
|
||||
|
||||
for (let j = 0; j < renderRowsForData.length; j++) {
|
||||
let renderRow = renderRowsForData[j];
|
||||
|
||||
const cache = this._cachedRenderRowsMap.get(renderRow.data)!;
|
||||
if (cache.has(renderRow.rowDef)) {
|
||||
cache.get(renderRow.rowDef)!.push(renderRow);
|
||||
} else {
|
||||
cache.set(renderRow.rowDef, [renderRow]);
|
||||
}
|
||||
renderRows.push(renderRow);
|
||||
}
|
||||
}
|
||||
|
||||
return renderRows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of `RenderRow<T>` for the provided data object and any `CdkRowDef` objects that
|
||||
* should be rendered for this data. Reuses the cached RenderRow objects if they match the same
|
||||
* `(T, CdkRowDef)` pair.
|
||||
*/
|
||||
private _getRenderRowsForData(
|
||||
data: T, dataIndex: number, cache?: WeakMap<CdkRowDef<T>, RenderRow<T>[]>): RenderRow<T>[] {
|
||||
const rowDefs = this._getRowDefs(data, dataIndex);
|
||||
|
||||
return rowDefs.map(rowDef => {
|
||||
const cachedRenderRows = (cache && cache.has(rowDef)) ? cache.get(rowDef)! : [];
|
||||
if (cachedRenderRows.length) {
|
||||
const dataRow = cachedRenderRows.shift()!;
|
||||
dataRow.dataIndex = dataIndex;
|
||||
return dataRow;
|
||||
} else {
|
||||
return {data, rowDef, dataIndex};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Update the map containing the content's column definitions. */
|
||||
private _cacheColumnDefs() {
|
||||
this._columnDefsByName.clear();
|
||||
@@ -545,9 +415,7 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke
|
||||
this._customRowDefs.forEach(rowDef => this._rowDefs.push(rowDef));
|
||||
|
||||
const defaultRowDefs = this._rowDefs.filter(def => !def.when);
|
||||
if (!this.multiTemplateDataRows && defaultRowDefs.length > 1) {
|
||||
throw getTableMultipleDefaultRowDefsError();
|
||||
}
|
||||
if (defaultRowDefs.length > 1) { throw getTableMultipleDefaultRowDefsError(); }
|
||||
this._defaultRowDef = defaultRowDefs[0];
|
||||
}
|
||||
|
||||
@@ -558,8 +426,12 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke
|
||||
private _renderUpdatedColumns() {
|
||||
// Re-render the rows when the row definition columns change.
|
||||
this._rowDefs.forEach(def => {
|
||||
if (def.getColumnsDiff()) {
|
||||
this._forceRenderRows();
|
||||
if (!!def.getColumnsDiff()) {
|
||||
// Reset the data to an empty array so that renderRowChanges will re-render all new rows.
|
||||
this._dataDiffer.diff([]);
|
||||
|
||||
this._rowOutlet.viewContainer.clear();
|
||||
this.renderRows();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -628,7 +500,7 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke
|
||||
this._renderChangeSubscription = dataStream
|
||||
.pipe(takeUntil(this._onDestroy))
|
||||
.subscribe(data => {
|
||||
this._data = data || [];
|
||||
this._data = data;
|
||||
this.renderRows();
|
||||
});
|
||||
}
|
||||
@@ -660,40 +532,28 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the matching row definitions that should be used for this row data. If there is only
|
||||
* one row definition, it is returned. Otherwise, find the row definitions that has a when
|
||||
* Finds the matching row definition that should be used for this row data. If there is only
|
||||
* one row definition, it is returned. Otherwise, find the row definition that has a when
|
||||
* predicate that returns true with the data. If none return true, return the default row
|
||||
* definition.
|
||||
*/
|
||||
_getRowDefs(data: T, dataIndex: number): CdkRowDef<T>[] {
|
||||
if (this._rowDefs.length == 1) { return [this._rowDefs[0]]; }
|
||||
_getRowDef(data: T, i: number): CdkRowDef<T> {
|
||||
if (this._rowDefs.length == 1) { return this._rowDefs[0]; }
|
||||
|
||||
let rowDefs: CdkRowDef<T>[] = [];
|
||||
if (this.multiTemplateDataRows) {
|
||||
rowDefs = this._rowDefs.filter(def => !def.when || def.when(dataIndex, data));
|
||||
} else {
|
||||
let rowDef =
|
||||
this._rowDefs.find(def => def.when && def.when(dataIndex, data)) || this._defaultRowDef;
|
||||
if (rowDef) {
|
||||
rowDefs.push(rowDef);
|
||||
}
|
||||
}
|
||||
let rowDef = this._rowDefs.find(def => def.when && def.when(i, data)) || this._defaultRowDef;
|
||||
if (!rowDef) { throw getTableMissingMatchingRowDefError(); }
|
||||
|
||||
if (!rowDefs.length) {
|
||||
throw getTableMissingMatchingRowDefError(data);
|
||||
}
|
||||
|
||||
return rowDefs;
|
||||
return rowDef;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the embedded view for the data row template and place it in the correct index location
|
||||
* within the data row view container.
|
||||
*/
|
||||
private _insertRow(renderRow: RenderRow<T>, renderIndex: number) {
|
||||
const rowDef = renderRow.rowDef;
|
||||
const context: RowContext<T> = {$implicit: renderRow.data};
|
||||
this._renderRow(this._rowOutlet, rowDef, context, renderIndex);
|
||||
private _insertRow(rowData: T, index: number) {
|
||||
const rowDef = this._getRowDef(rowData, index);
|
||||
const context: CdkCellOutletRowContext<T> = {$implicit: rowData};
|
||||
this._renderRow(this._rowOutlet, rowDef, context, index);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -702,7 +562,7 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke
|
||||
* of where to place the new row template in the outlet.
|
||||
*/
|
||||
private _renderRow(
|
||||
outlet: RowOutlet, rowDef: BaseRowDef, context: RowContext<T> = {}, index = 0) {
|
||||
outlet: RowOutlet, rowDef: BaseRowDef, context: CdkCellOutletRowContext<T> = {}, index = 0) {
|
||||
// TODO(andrewseguin): enforce that one outlet was instantiated from createEmbeddedView
|
||||
outlet.viewContainer.createEmbeddedView(rowDef.template, context, index);
|
||||
|
||||
@@ -721,21 +581,14 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke
|
||||
*/
|
||||
private _updateRowIndexContext() {
|
||||
const viewContainer = this._rowOutlet.viewContainer;
|
||||
for (let renderIndex = 0, count = viewContainer.length; renderIndex < count; renderIndex++) {
|
||||
const viewRef = viewContainer.get(renderIndex) as RowViewRef<T>;
|
||||
const context = viewRef.context as RowContext<T>;
|
||||
context.count = count;
|
||||
context.first = renderIndex === 0;
|
||||
context.last = renderIndex === count - 1;
|
||||
context.even = renderIndex % 2 === 0;
|
||||
context.odd = !context.even;
|
||||
|
||||
if (this.multiTemplateDataRows) {
|
||||
context.dataIndex = this._renderRows[renderIndex].dataIndex;
|
||||
context.renderIndex = renderIndex;
|
||||
} else {
|
||||
context.index = this._renderRows[renderIndex].dataIndex;
|
||||
}
|
||||
for (let index = 0, count = viewContainer.length; index < count; index++) {
|
||||
const viewRef = viewContainer.get(index) as RowViewRef<T>;
|
||||
viewRef.context.index = index;
|
||||
viewRef.context.count = count;
|
||||
viewRef.context.first = index === 0;
|
||||
viewRef.context.last = index === count - 1;
|
||||
viewRef.context.even = index % 2 === 0;
|
||||
viewRef.context.odd = !viewRef.context.even;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -767,15 +620,4 @@ export class CdkTable<T> implements CollectionViewer, OnInit, AfterContentChecke
|
||||
this._elementRef.nativeElement.appendChild(element);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces a re-render of the data rows. Should be called in cases where there has been an input
|
||||
* change that affects the evaluation of which rows should be rendered, e.g. toggling
|
||||
* `multiTemplateDataRows` or adding/removing row definitions.
|
||||
*/
|
||||
private _forceRenderRows() {
|
||||
this._dataDiffer.diff([]);
|
||||
this._rowOutlet.viewContainer.clear();
|
||||
this.renderRows();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
<mat-card>
|
||||
<table mat-table [dataSource]="dataSource" multiTemplateDataRows matSort>
|
||||
<ng-container matColumnDef="{{column}}" *ngFor="let column of columnsToDisplay">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> {{column}} </th>
|
||||
<td mat-cell *matCellDef="let element"> {{element[column]}} </td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Expanded Content Column - The detail row is made up of this one column that spans across all columns -->
|
||||
<ng-container matColumnDef="expandedDetail">
|
||||
<td colspan="4" mat-cell *matCellDef="let element">
|
||||
<div style="overflow: hidden"
|
||||
[@detailExpand]="element == expandedElement ? 'expanded' : 'collapsed'">
|
||||
The symbol for {{element.name}} is {{element.symbol}}
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="columnsToDisplay"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: columnsToDisplay;"
|
||||
class="demo-element-row"
|
||||
[class.demo-expanded]="expandedElement == row"
|
||||
(click)="expandedElement = expandedElement === row ? undefined : row"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: ['expandedDetail']"
|
||||
class="demo-detail-row"></tr>
|
||||
</table>
|
||||
|
||||
<mat-paginator pageSize="20" [pageSizeOptions]="[5, 20]"></mat-paginator>
|
||||
</mat-card>
|
||||
@@ -1,60 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Component, ViewChild} from '@angular/core';
|
||||
import {Element, ELEMENT_DATA} from 'table/element-data';
|
||||
import {animate, state, style, transition, trigger} from '@angular/animations';
|
||||
import {MatPaginator, MatSort, MatTableDataSource} from '@angular/material';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
selector: 'expandable-rows',
|
||||
templateUrl: 'expandable-rows.html',
|
||||
animations: [
|
||||
trigger('detailExpand', [
|
||||
state('collapsed', style({height: '0px', minHeight: '0'})),
|
||||
state('expanded', style({height: '*'})),
|
||||
transition('expanded <=> collapsed',
|
||||
animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')),
|
||||
]),
|
||||
],
|
||||
styles: [`
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
tr.demo-detail-row {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
tr.demo-element-row:not(.demo-expanded):hover {
|
||||
background: #F5F5F5;
|
||||
}
|
||||
|
||||
tr.demo-element-row:not(.demo-expanded):active {
|
||||
background: #EFEFEF;
|
||||
}
|
||||
|
||||
.demo-element-row td {
|
||||
border-bottom-width: 0;
|
||||
}
|
||||
`]
|
||||
})
|
||||
export class ExpandableRowsDemo {
|
||||
dataSource = new MatTableDataSource(ELEMENT_DATA.slice());
|
||||
columnsToDisplay = ['name', 'weight', 'symbol', 'position'];
|
||||
expandedElement: Element;
|
||||
|
||||
@ViewChild(MatSort) sort: MatSort;
|
||||
@ViewChild(MatPaginator) paginator: MatPaginator;
|
||||
|
||||
ngOnInit() {
|
||||
this.dataSource.sort = this.sort;
|
||||
this.dataSource.paginator = this.paginator;
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,6 @@ import {MatTableDataSourceDemo} from './mat-table-data-source/mat-table-data-sou
|
||||
import {DynamicColumnsDemo} from './dynamic-columns/dynamic-columns';
|
||||
import {RowContextDemo} from './row-context/row-context';
|
||||
import {WhenRowsDemo} from './when-rows/when-rows';
|
||||
import {ExpandableRowsDemo} from './expandable-rows/expandable-rows';
|
||||
|
||||
export const TABLE_DEMO_ROUTES: Routes = [
|
||||
{path: '', redirectTo: 'main-demo', pathMatch: 'full'},
|
||||
@@ -24,6 +23,5 @@ export const TABLE_DEMO_ROUTES: Routes = [
|
||||
{path: 'mat-table-data-source', component: MatTableDataSourceDemo},
|
||||
{path: 'dynamic-columns', component: DynamicColumnsDemo},
|
||||
{path: 'row-context', component: RowContextDemo},
|
||||
{path: 'when-rows', component: WhenRowsDemo},
|
||||
{path: 'expandable-rows', component: ExpandableRowsDemo}
|
||||
{path: 'when-rows', component: WhenRowsDemo}
|
||||
];
|
||||
|
||||
@@ -16,14 +16,11 @@ import {
|
||||
MatCardModule,
|
||||
MatCheckboxModule,
|
||||
MatIconModule,
|
||||
MatInputModule,
|
||||
MatMenuModule,
|
||||
MatInputModule, MatMenuModule,
|
||||
MatPaginatorModule,
|
||||
MatRadioModule,
|
||||
MatSlideToggleModule,
|
||||
MatSortModule,
|
||||
MatTableModule,
|
||||
MatTabsModule
|
||||
MatTableModule, MatTabsModule
|
||||
} from '@angular/material';
|
||||
import {FormsModule} from '@angular/forms';
|
||||
import {CdkTableModule} from '@angular/cdk/table';
|
||||
@@ -36,7 +33,6 @@ import {MatTableDataSourceDemo} from './mat-table-data-source/mat-table-data-sou
|
||||
import {DynamicColumnsDemo} from './dynamic-columns/dynamic-columns';
|
||||
import {RowContextDemo} from './row-context/row-context';
|
||||
import {WhenRowsDemo} from './when-rows/when-rows';
|
||||
import {ExpandableRowsDemo} from 'table/expandable-rows/expandable-rows';
|
||||
|
||||
|
||||
@NgModule({
|
||||
@@ -52,7 +48,6 @@ import {ExpandableRowsDemo} from 'table/expandable-rows/expandable-rows';
|
||||
MatMenuModule,
|
||||
MatPaginatorModule,
|
||||
MatRadioModule,
|
||||
MatSlideToggleModule,
|
||||
MatSortModule,
|
||||
MatTableModule,
|
||||
MatTabsModule,
|
||||
@@ -69,7 +64,6 @@ import {ExpandableRowsDemo} from 'table/expandable-rows/expandable-rows';
|
||||
DynamicColumnsDemo,
|
||||
RowContextDemo,
|
||||
WhenRowsDemo,
|
||||
ExpandableRowsDemo
|
||||
],
|
||||
providers: [
|
||||
PeopleDatabase
|
||||
|
||||
@@ -21,7 +21,6 @@ export class TableDemoPage {
|
||||
{name: 'MatTableDataSource', link: 'mat-table-data-source'},
|
||||
{name: 'Dynamic Columns', link: 'dynamic-columns'},
|
||||
{name: 'Row Context', link: 'row-context'},
|
||||
{name: 'When Rows', link: 'when-rows'},
|
||||
{name: 'Expandable Rows', link: 'expandable-rows'}
|
||||
{name: 'When Rows', link: 'when-rows'}
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1,68 +1,13 @@
|
||||
<div>
|
||||
<button mat-raised-button (click)="shuffleArray()">Shuffle array</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button mat-raised-button (click)="changeRandomNumber()">Randomize</button>
|
||||
Highlighted rows: {{randomNumber}}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<mat-slide-toggle (change)="multiTemplateDataRows = $event.checked">
|
||||
Enable multiple data rows
|
||||
</mat-slide-toggle>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<mat-slide-toggle (change)="useTrackByValue = $event.checked">
|
||||
Enable track by value
|
||||
</mat-slide-toggle>
|
||||
</div>
|
||||
|
||||
<mat-card>
|
||||
<h3> MatTable </h3>
|
||||
|
||||
<table mat-table style="width: 100%"
|
||||
[trackBy]="useTrackByValue ? trackByValue : undefined"
|
||||
[multiTemplateDataRows]="multiTemplateDataRows"
|
||||
[dataSource]="data">
|
||||
<!-- Data column -->
|
||||
<ng-container matColumnDef="data">
|
||||
<th mat-header-cell *matHeaderCellDef> Data </th>
|
||||
<td mat-cell *matCellDef="let data">
|
||||
{{data.value}}
|
||||
</td>
|
||||
<table cdk-table [dataSource]="dataSource">
|
||||
<ng-container cdkColumnDef="{{column}}" *ngFor="let column of columns">
|
||||
<th cdk-header-cell *cdkHeaderCellDef> {{column}} </th>
|
||||
<td cdk-cell *cdkCellDef="let element"> {{element[column]}} </td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Index column -->
|
||||
<ng-container matColumnDef="index">
|
||||
<th mat-header-cell *matHeaderCellDef> Index </th>
|
||||
<td mat-cell *matCellDef="let data; let index = index">
|
||||
{{index}}
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr cdk-header-row *cdkHeaderRowDef="columns"></tr>
|
||||
|
||||
<!-- Data index column -->
|
||||
<ng-container matColumnDef="dataIndex">
|
||||
<th mat-header-cell *matHeaderCellDef> Data Index </th>
|
||||
<td mat-cell *matCellDef="let data; let dataIndex = dataIndex">
|
||||
{{dataIndex}}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<!-- Row Index column -->
|
||||
<ng-container matColumnDef="renderIndex">
|
||||
<th mat-header-cell *matHeaderCellDef> Render Index </th>
|
||||
<td mat-cell *matCellDef="let data; let renderIndex = renderIndex">
|
||||
{{renderIndex}}
|
||||
</td>
|
||||
</ng-container>
|
||||
|
||||
<tr mat-header-row *matHeaderRowDef="columnsToDisplay"></tr>
|
||||
|
||||
<tr mat-row style="background: rgba(255, 0, 0, .2);"
|
||||
*matRowDef="let data; columns: columnsToDisplay; when: whenFn"></tr>
|
||||
<tr mat-row style="background: rgba(0, 255, 0, .2);"
|
||||
*matRowDef="let data; columns: columnsToDisplay;"></tr>
|
||||
<tr cdk-row *cdkRowDef="let row; let odd = odd; columns: ['position']; when: isOdd"></tr>
|
||||
<tr cdk-row *cdkRowDef="let row; columns: columns;"></tr>
|
||||
</table>
|
||||
</mat-card>
|
||||
|
||||
@@ -6,57 +6,16 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Component, ViewChild} from '@angular/core';
|
||||
import {MatTable} from '@angular/material';
|
||||
|
||||
const DATA_LENGTH = 10;
|
||||
|
||||
export interface DemoDataObject {
|
||||
value: boolean;
|
||||
}
|
||||
import {Component} from '@angular/core';
|
||||
import {Element, ELEMENT_DATA} from '../element-data';
|
||||
|
||||
@Component({
|
||||
moduleId: module.id,
|
||||
templateUrl: 'when-rows.html',
|
||||
})
|
||||
export class WhenRowsDemo {
|
||||
columnsToDisplay = ['data', 'index', 'dataIndex', 'renderIndex'];
|
||||
data: DemoDataObject[] =
|
||||
(new Array(DATA_LENGTH) as DemoDataObject[]).fill({value: false}, 0, DATA_LENGTH);
|
||||
randomNumber = 0;
|
||||
multiTemplateDataRows = false;
|
||||
useTrackByValue = false;
|
||||
columns = ['name', 'weight', 'symbol', 'position'];
|
||||
dataSource: Element[] = ELEMENT_DATA.slice();
|
||||
|
||||
whenFn = (_i: number, d: DemoDataObject) => d.value;
|
||||
trackByValue = (_i: number, d: DemoDataObject) => d.value;
|
||||
|
||||
@ViewChild(MatTable) table: MatTable<any>;
|
||||
|
||||
constructor() {
|
||||
this.changeRandomNumber();
|
||||
}
|
||||
|
||||
changeRandomNumber() {
|
||||
this.randomNumber = Math.floor(Math.random() * DATA_LENGTH);
|
||||
this.data = this.data.map((_d: DemoDataObject, i: number) => ({value: i < this.randomNumber}));
|
||||
if (this.table) {
|
||||
this.table.renderRows();
|
||||
}
|
||||
}
|
||||
|
||||
shuffleArray() {
|
||||
let dataToShuffle = this.data.slice();
|
||||
let currentIndex = dataToShuffle.length;
|
||||
while (0 !== currentIndex) {
|
||||
let randomIndex = Math.floor(Math.random() * currentIndex);
|
||||
currentIndex -= 1;
|
||||
|
||||
// Swap
|
||||
let temp = dataToShuffle[currentIndex];
|
||||
dataToShuffle[currentIndex] = dataToShuffle[randomIndex];
|
||||
dataToShuffle[randomIndex] = temp;
|
||||
}
|
||||
|
||||
this.data = dataToShuffle;
|
||||
}
|
||||
isOdd = (i: number, _d: Element) => i % 2 !== 0;
|
||||
}
|
||||
|
||||
@@ -56,23 +56,6 @@ describe('MatTable', () => {
|
||||
['Footer A', 'Footer B', 'Footer C'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should create a table with multiTemplateDataRows true', () => {
|
||||
let fixture = TestBed.createComponent(MatTableWithWhenRowApp);
|
||||
fixture.componentInstance.multiTemplateDataRows = true;
|
||||
fixture.detectChanges();
|
||||
|
||||
const tableElement = fixture.nativeElement.querySelector('.mat-table');
|
||||
expectTableToMatchContent(tableElement, [
|
||||
['Column A', 'Column B', 'Column C'],
|
||||
['a_1'],
|
||||
['a_2'],
|
||||
['a_3'],
|
||||
['a_4'], // With multiple rows, this row shows up along with the special 'when' fourth_row
|
||||
['fourth_row'],
|
||||
['Footer A', 'Footer B', 'Footer C'],
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should be able to render a table correctly with native elements', () => {
|
||||
@@ -503,7 +486,7 @@ class NativeHtmlTableApp {
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<mat-table [dataSource]="dataSource" [multiTemplateDataRows]="multiTemplateDataRows">
|
||||
<mat-table [dataSource]="dataSource">
|
||||
<ng-container matColumnDef="column_a">
|
||||
<mat-header-cell *matHeaderCellDef> Column A</mat-header-cell>
|
||||
<mat-cell *matCellDef="let row"> {{row.a}}</mat-cell>
|
||||
@@ -522,7 +505,6 @@ class NativeHtmlTableApp {
|
||||
`
|
||||
})
|
||||
class MatTableWithWhenRowApp {
|
||||
multiTemplateDataRows = false;
|
||||
dataSource: FakeDataSource | null = new FakeDataSource();
|
||||
isFourthRow = (i: number, _rowData: TestData) => i == 3;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user