The <dt-tree-table>
provides the functionality of a tree displayed in a
grid/table. The API is very similar to the
<dt-table>
component. You define the different columns
and rows and choose which columns should be rendered inside each row.
It is also possible to have multiple different row templates that can be switched when a row should have a special behavior e.g. a show more row for lazy loading more rows.
In a tree-table it is never possible to sort for a column.
<dt-tree-table [dataSource]="dataSource" [treeControl]="treeControl">
<ng-container dtColumnDef="name">
<dt-tree-table-header-cell *dtHeaderCellDef>
Name
</dt-tree-table-header-cell>
<dt-tree-table-toggle-cell *dtCellDef="let row">
{{ row.name }}
</dt-tree-table-toggle-cell>
</ng-container>
<ng-container dtColumnDef="blocked" dtColumnAlign="right">
<dt-tree-table-header-cell *dtHeaderCellDef>
Blocked
</dt-tree-table-header-cell>
<dt-cell *dtCellDef="let row">{{ row.blocked }}ms</dt-cell>
</ng-container>
<ng-container dtColumnDef="running" dtColumnAlign="center">
<dt-tree-table-header-cell *dtHeaderCellDef>
Running
</dt-tree-table-header-cell>
<dt-cell *dtCellDef="let row">{{ row.running }}ms</dt-cell>
</ng-container>
<ng-container dtColumnDef="waiting">
<dt-tree-table-header-cell *dtHeaderCellDef>
Waiting
</dt-tree-table-header-cell>
<dt-cell *dtCellDef="let row">{{ row.waiting }}ms</dt-cell>
</ng-container>
<dt-header-row
*dtHeaderRowDef="['name', 'blocked', 'running', 'waiting']"
></dt-header-row>
<dt-tree-table-row
*dtRowDef="let row; columns: ['name', 'blocked', 'running', 'waiting']"
[data]="row"
></dt-tree-table-row>
</dt-tree-table>
Imports
You have to import the DtTreeTableModule
when you want to use the
<dt-tree-table>
.
@NgModule({
imports: [DtTreeTableModule],
})
class MyModule {}
Initialization
Start by adding the <dt-tree-table>
component to your template and provide it
with a datasource and a treecontrol.
<dt-tree-table
[dataSource]="dataSource"
[treeControl]="treeControl"
></dt-tree-table>
Provide the data
Since a table is a flat structure but we want to show hierachical tree data
there is a special datasource called DtTreeDataSource
involved. The
DtTreeControl
is responsible for controlling expanding/collapsing of nodes.
private _getLevel = (node: ThreadFlatNode) => node.level;
private _isExpandable = (node: ThreadFlatNode) => node.expandable;
this.treeControl = new DtTreeControl<ThreadFlatNode>(this._getLevel, this._isExpandable);
The DtTreeFlattener
takes multiple accessor functions as parameters to
transform the hierachical data to a flat datastructure that can be rendered
inside the table. The DtTreeFlattener
is a generic class that transforms a
hierachical structure of nodes of type T to a flat structure of nodes of type F;
In the example below it takes the hierachical type ThreadNode
and transforms
it to ThreadFlatNode
.
private _transformer = (node: ThreadNode, level: number): ThreadFlatNode => {
...
return flatNode;
}
private _getLevel = (node: ThreadFlatNode) => node.level;
private _isExpandable = (node: ThreadFlatNode) => node.expandable;
private _getChildren = (node: ThreadNode): ThreadNode[] => node.children || [];
this.treeFlattener = new DtTreeFlattener<ThreadNode, ThreadFlatNode>(this._transformer, this._getLevel, this._isExpandable, this._getChildren);
The DtTreeDataSource
gets the DtTreeFlattener
and the DtTreeControl
as
parameters.
this.dataSource = new DtTreeDataSource(this.treeControl, this.treeFlattener);
By assigning the data property on the datasource with a new set of data the tree-table is populated with the rows.
this.dataSource.data = [
{
name: 'hz.hzInstance_1_cluster.thread',
icon: 'apache-tomcat',
threadlevel: 'S0',
totalTimeConsumption: 150,
waiting: 123,
running: 20,
blocked: 0,
children: [
{
name: 'hz.hzInstance_1_cluster.thread_1_hz.hzInstance_1_cluster.thread-1',
icon: 'apache-tomcat',
threadlevel: 'S1',
totalTimeConsumption: 150,
waiting: 123,
running: 20,
blocked: 0,
},
{
name: 'hz.hzInstance_1_cluster.thread-2',
icon: 'apache-tomcat',
threadlevel: 'S1',
totalTimeConsumption: 150,
waiting: 130,
running: 0,
blocked: 0,
},
],
},
];
Adding column templates
To specify the different columns that are shown inside the table you have to
define column templates. Each column consists of <dt-tree-table-header-cell>
and either a <dt-tree-table-toggle-cell>
or a <dt-cell>
inside a
<ng-container>
.
The first column shown in the <dt-tree-table>
has to include the
<dt-tree-table-toggle-cell>
. This special cell includes the indentation logic
and the toggle button used to expand/collapse the next level of rows.
<ng-container dtColumnDef="name">
<dt-tree-table-header-cell *dtHeaderCellDef>Name</dt-tree-table-header-cell>
<dt-tree-table-toggle-cell *dtCellDef="let row">
{{row.name}}
</dt-tree-table-toggle-cell>
</ng-container>
A normal column could like the following example
<ng-container dtColumnDef="waiting">
<dt-tree-table-header-cell *dtHeaderCellDef>
Waiting
</dt-tree-table-header-cell>
<dt-cell *dtCellDef="let row"> {{row.waiting}} </dt-cell>
</ng-container>
After adding all column definitions the next step is to add the row definitions.
Adding rows
You want to add the table header by adding a <dt-header-row>
inside the
<dt-tree-table>
tag.
<dt-header-row
*dtHeaderRowDef="['name', 'total', 'blocked', 'running', 'waiting', 'actions']"
></dt-header-row>
The next step is to add the row where the columns get rendered. You can do this
by adding the <dt-tree-table-row>
inside the <dt-tree-table>
tag. You can
specify the columns that should be rendered and don't forget to bind the row's
data to the <dt-tree-table-row>
s data input [data]="row"
.
<dt-tree-table-row
*dtRowDef="let row; columns: ['name', 'total', 'blocked', 'running', 'waiting', 'actions'];"
[data]="row"
></dt-tree-table-row>
Column alignment
If you want to specify an alignment for the content of a column you can do so by
setting the dtColumnAlign
input on the
<ng-container dtColumnDef=... dtColumnAlign="text">
The dtColumnAlign
input handles the following values:
'left' | 'right' | 'center'
or one of the typed values that get mapped to
left, right and center internally
'text' | 'id' | 'icon' | 'control' | 'number' | 'date' | 'ip'
.
DtIndicator
You can use the DtIndicator
directive the same way as in the <dt-table>
to
indicate a warning/error in a row. This will result in a Problem indicator being
shown in front of the row.
<dt-tree-table [dataSource]="dataSource" [treeControl]="treeControl">
<ng-container dtColumnDef="name">
<dt-tree-table-header-cell *dtHeaderCellDef>
Name
</dt-tree-table-header-cell>
<dt-tree-table-toggle-cell *dtCellDef="let row">
<dt-info-group>
<dt-info-group-icon>
<dt-icon [name]="row.icon"></dt-icon>
</dt-info-group-icon>
<dt-info-group-title>{{ row.name }}</dt-info-group-title>
{{ row.threadlevel }}
</dt-info-group>
</dt-tree-table-toggle-cell>
</ng-container>
<ng-container dtColumnDef="blocked" dtColumnAlign="right">
<dt-tree-table-header-cell *dtHeaderCellDef>
Blocked
</dt-tree-table-header-cell>
<dt-cell
*dtCellDef="let row"
[dtIndicator]="row.blocked > 0"
dtIndicatorColor="error"
>
{{ row.blocked }}ms
</dt-cell>
</ng-container>
<ng-container dtColumnDef="running" dtColumnAlign="center">
<dt-tree-table-header-cell *dtHeaderCellDef>
Running
</dt-tree-table-header-cell>
<dt-cell *dtCellDef="let row">{{ row.running }}ms</dt-cell>
</ng-container>
<ng-container dtColumnDef="waiting">
<dt-tree-table-header-cell *dtHeaderCellDef>
Waiting
</dt-tree-table-header-cell>
<dt-cell *dtCellDef="let row">{{ row.waiting }}ms</dt-cell>
</ng-container>
<ng-container dtColumnDef="actions">
<dt-tree-table-header-cell *dtHeaderCellDef>
Actions
</dt-tree-table-header-cell>
<dt-cell *dtCellDef="let row">
<button dt-icon-button variant="nested" aria-label="Filter results">
<dt-icon name="filter"></dt-icon>
</button>
<button
dt-icon-button
variant="nested"
[dtContextDialogTrigger]="dialog"
aria-label="Show more data"
>
<dt-icon name="more"></dt-icon>
</button>
<dt-context-dialog
#dialog
aria-label="Show more data"
ariaLabelClose="Close context dialog"
>
{{ row.name }} context dialog
</dt-context-dialog>
</dt-cell>
</ng-container>
<dt-header-row
*dtHeaderRowDef="['name', 'blocked', 'running', 'waiting', 'actions']"
></dt-header-row>
<dt-tree-table-row
*dtRowDef="
let row;
columns: ['name', 'blocked', 'running', 'waiting', 'actions']
"
[data]="row"
></dt-tree-table-row>
</dt-tree-table>
DtTreeTable inputs
Name | Type | Default | Description |
---|---|---|---|
treeControl |
DtTreeControl<T> |
- | Input for the treeControl that handles expand/collapse of rows |
hasInteractiveRows |
boolean |
false |
Input wether the tree-table should have interactive rows - results in a hover effect |
trackBy |
Fn(index, T) |
Tracking function that will be used to check the differences in data changes. Used similarly to ngFor trackBy function. |
It is obligatory to provide either an aria-label
or aria-labelledby
.
DtTreeTableRow inputs
Name | Type | Description |
---|---|---|
data |
T |
The data for the row. Note that this might be removed rather soon and made obsolete due to a feature request on the underlying cdk table |
DtTreeTableToggleCell inputs
Name | Type | Default | Description |
---|---|---|---|
expanded |
boolean |
false |
Sets or gets the DtTreeTableToggleCell's expanded state. |
DtTreeTableToggleCell outputs
Name | Type | Description |
---|---|---|
expandChange |
EventEmitter<boolean> |
Emits an event when the expanded state changes. The event parameter is set to true when it is expanded |
expanded |
EventEmitter<void> |
Event emitted when the DtTreeTableToggleCell is expanded. |
collapsed |
EventEmitter<void> |
Event emitted when the DtTreeTableToggleCell is collapsed. |
DtTreeControl
Name | Type | Default | Description |
---|---|---|---|
dataNodes |
T[] |
Saved row data for expandAll action | |
expansionModel |
SelectionModel<T> |
A selection model to track expansion status | |
getChildren |
(row: T) => (Observable<T[]> | T[] | undefined | null) |
Function that returns an observable containing the child rows, the child rows, undefined or null | |
getLevel |
(row: T) => number |
accessor fn for the level of the row | |
isExpandable |
(dataNode: T) => boolean |
function that returns wether a node is expandable |
Methods
Name | Return value | Description |
---|---|---|
collapse(row: T) |
void |
collapses one single row |
collapseAll |
void |
collapses all rows |
collapseDescendants(row: T) |
void |
collapses all descendants of a row |
expand(row: T) |
void |
expands one single row |
expandAll |
void |
expands all rows |
expandDescendants(row: T) |
void |
expands all descendants of a row |
getDescendants(row: T) |
T[] |
Gets a list of descendant rows |
isExpanded(row: T) |
boolean |
Wether a row is expanded |
toggle(row: T) |
void |
Toggles a single row |
toggleDescendants(row: T) |
void |
Toggles descendants of a row |
DtTreeFlattener<T, F>
Parameters | Type | Description |
---|---|---|
getChildren |
(row: T) => (Observable<T[]> | T[] | undefined | null) |
function that returns an observable containing the child rows, the child rows, undefined or null |
getLevel |
(row: T) => number |
accessor fn for the level of the row |
isExpandable |
(row: T) => boolean |
function that returns wether a node is expandable |
transformFunction |
(row: T, level: number) => F |
function that transforms from type T to flat type F |
Tree table in use
Rows with multiple lines
Use the dt-info-group
for rows with multiple lines of text and icons.
<dt-tree-table [dataSource]="dataSource" [treeControl]="treeControl">
<ng-container dtColumnDef="name">
<dt-tree-table-header-cell *dtHeaderCellDef>
Name
</dt-tree-table-header-cell>
<dt-tree-table-toggle-cell *dtCellDef="let row">
<dt-info-group>
<dt-info-group-icon>
<dt-icon [name]="row.icon"></dt-icon>
</dt-info-group-icon>
<dt-info-group-title>{{ row.name }}</dt-info-group-title>
{{ row.threadlevel }}
</dt-info-group>
</dt-tree-table-toggle-cell>
</ng-container>
<ng-container dtColumnDef="total">
<dt-tree-table-header-cell *dtHeaderCellDef>
Total time consumption
</dt-tree-table-header-cell>
<dt-cell *dtCellDef="let row">
<dt-progress-bar
class="dt-thread-progress"
[value]="row.blocked"
[max]="row.totalTimeConsumption"
aria-label="Blocked resources progress"
></dt-progress-bar>
<dt-progress-bar
class="dt-thread-progress"
[value]="row.waiting"
[max]="row.totalTimeConsumption"
aria-label="Waiting resources progress"
></dt-progress-bar>
<dt-progress-bar
class="dt-thread-progress"
[value]="row.running"
[max]="row.totalTimeConsumption"
aria-label="Running resources progress"
></dt-progress-bar>
</dt-cell>
</ng-container>
<ng-container dtColumnDef="blocked" dtColumnAlign="right">
<dt-tree-table-header-cell *dtHeaderCellDef>
Blocked
</dt-tree-table-header-cell>
<dt-cell *dtCellDef="let row">{{ row.blocked }}ms</dt-cell>
</ng-container>
<ng-container dtColumnDef="running" dtColumnAlign="center">
<dt-tree-table-header-cell *dtHeaderCellDef>
Running
</dt-tree-table-header-cell>
<dt-cell *dtCellDef="let row">{{ row.running }}ms</dt-cell>
</ng-container>
<ng-container dtColumnDef="waiting">
<dt-tree-table-header-cell *dtHeaderCellDef>
Waiting
</dt-tree-table-header-cell>
<dt-cell *dtCellDef="let row">{{ row.waiting }}ms</dt-cell>
</ng-container>
<ng-container dtColumnDef="actions">
<dt-tree-table-header-cell *dtHeaderCellDef>
Actions
</dt-tree-table-header-cell>
<dt-cell *dtCellDef="let row">
<button dt-icon-button variant="nested" aria-label="Filter results">
<dt-icon name="filter"></dt-icon>
</button>
<button
dt-icon-button
variant="nested"
[dtContextDialogTrigger]="dialog"
aria-label="Show more data"
>
<dt-icon name="more"></dt-icon>
</button>
<dt-context-dialog
#dialog
aria-label="Show more data"
ariaLabelClose="Close context dialog"
>
{{ row.name }} context dialog
</dt-context-dialog>
</dt-cell>
</ng-container>
<dt-header-row
*dtHeaderRowDef="[
'name',
'total',
'blocked',
'running',
'waiting',
'actions'
]"
></dt-header-row>
<dt-tree-table-row
*dtRowDef="
let row;
columns: ['name', 'total', 'blocked', 'running', 'waiting', 'actions']
"
[data]="row"
></dt-tree-table-row>
</dt-tree-table>
<button dt-button (click)="treeControl.expandAll()">Expand all</button>
<button dt-button (click)="treeControl.collapseAll()">Collapse all</button>
Load entries asynchronously
In some cases there are too many hierarchy levels or too many entries in a tree, it's not possible to show them all at once. In this case one row can be used for an expand button which will load all entries at once.
<dt-tree-table [dataSource]="dataSource" [treeControl]="treeControl">
<ng-container dtColumnDef="name">
<dt-tree-table-header-cell *dtHeaderCellDef>
Name
</dt-tree-table-header-cell>
<dt-tree-table-toggle-cell *dtCellDef="let row">
<dt-info-group>
<dt-info-group-icon>
<dt-icon [name]="row.icon"></dt-icon>
</dt-info-group-icon>
<dt-info-group-title>{{ row.name }}</dt-info-group-title>
{{ row.threadlevel }}
</dt-info-group>
</dt-tree-table-toggle-cell>
</ng-container>
<ng-container dtColumnDef="total">
<dt-tree-table-header-cell *dtHeaderCellDef>
Total time consumption
</dt-tree-table-header-cell>
<dt-cell *dtCellDef="let row">
<ng-container *ngIf="!row.isShowMore">
<dt-progress-bar
class="thread-progress"
[value]="row.blocked"
[max]="row.totalTimeConsumption"
aria-label="Blocked resources progress"
></dt-progress-bar>
<dt-progress-bar
class="thread-progress"
[value]="row.waiting"
[max]="row.totalTimeConsumption"
aria-label="Waiting resources progress"
></dt-progress-bar>
<dt-progress-bar
class="thread-progress"
[value]="row.running"
[max]="row.totalTimeConsumption"
aria-label="Running resources progress"
></dt-progress-bar>
</ng-container>
</dt-cell>
</ng-container>
<ng-container dtColumnDef="blocked" dtColumnAlign="right">
<dt-tree-table-header-cell *dtHeaderCellDef>
Blocked
</dt-tree-table-header-cell>
<dt-cell *dtCellDef="let row">
<ng-container *ngIf="!row.isShowMore"> {{ row.blocked }}ms </ng-container>
</dt-cell>
</ng-container>
<ng-container dtColumnDef="running" dtColumnAlign="center">
<dt-tree-table-header-cell *dtHeaderCellDef>
Running
</dt-tree-table-header-cell>
<dt-cell *dtCellDef="let row">
<ng-container *ngIf="!row.isShowMore"> {{ row.running }}ms </ng-container>
</dt-cell>
</ng-container>
<ng-container dtColumnDef="waiting">
<dt-tree-table-header-cell *dtHeaderCellDef>
Waiting
</dt-tree-table-header-cell>
<dt-cell *dtCellDef="let row">
<ng-container *ngIf="!row.isShowMore"> {{ row.waiting }}ms </ng-container>
</dt-cell>
</ng-container>
<ng-container dtColumnDef="showMore">
<dt-tree-table-toggle-cell *dtCellDef="let row">
<button dt-button variant="secondary" (click)="showMore()">
Show more
</button>
</dt-tree-table-toggle-cell>
</ng-container>
<ng-container dtColumnDef="actions">
<dt-tree-table-header-cell *dtHeaderCellDef>
Actions
</dt-tree-table-header-cell>
<dt-cell *dtCellDef="let row">
<ng-container *ngIf="!row.isShowMore">
<button dt-icon-button variant="nested" aria-label="Filter results">
<dt-icon name="filter"></dt-icon>
</button>
<button
dt-icon-button
variant="nested"
[dtContextDialogTrigger]="dialog"
aria-label="Show more data"
>
<dt-icon name="more"></dt-icon>
</button>
<dt-context-dialog
#dialog
aria-label="Show more data"
ariaLabelClose="Close context dialog"
>
{{ row.name }} context dialog
</dt-context-dialog>
</ng-container>
</dt-cell>
</ng-container>
<dt-header-row
*dtHeaderRowDef="[
'name',
'total',
'blocked',
'running',
'waiting',
'actions'
]"
></dt-header-row>
<dt-tree-table-row
*dtRowDef="
let row;
columns: ['name', 'total', 'blocked', 'running', 'waiting', 'actions']
"
[data]="row"
></dt-tree-table-row>
<dt-tree-table-row
*dtRowDef="
let row;
columns: [
'showMore',
'total',
'blocked',
'running',
'waiting',
'actions'
];
when: isShowMore
"
[data]="row"
></dt-tree-table-row>
</dt-tree-table>
Responsive behavior
As long as a screen is big enough the tree-table can show all desired columns. Once a screen width decreases columns disappear according to their importance for a specific use case. The least important column will disappear first. The only column always visible is the tree itself (name of entity).