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.
´
Loading interactive demo...
<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 >
export class TreeTableSimpleExample {
treeControl: FlatTreeControl<ThreadFlatNode>;
treeFlattener: DtTreeFlattener<ThreadNode, ThreadFlatNode>;
dataSource: DtTreeDataSource<ThreadNode, ThreadFlatNode>;
constructor ( ) {
this .treeControl = new DtTreeControl<ThreadFlatNode>(
this ._getLevel,
this ._isExpandable,
);
this .treeFlattener = new DtTreeFlattener(
this .transformer,
this ._getLevel,
this ._isExpandable,
this ._getChildren,
);
this .dataSource = new DtTreeDataSource(
this .treeControl,
this .treeFlattener,
);
this .dataSource.data = TESTDATA;
}
hasChild = (_: number , _nodeData: ThreadFlatNode ) => _nodeData.expandable;
transformer = (node: ThreadNode, level: number ): ThreadFlatNode => {
const flatNode = new ThreadFlatNode();
flatNode.name = node.name;
flatNode.level = level;
flatNode.expandable = !!node.children;
flatNode.blocked = node.blocked;
flatNode.running = node.running;
flatNode.waiting = node.waiting;
return flatNode;
};
private _getLevel = (node: ThreadFlatNode ) => node.level;
private _isExpandable = (node: ThreadFlatNode ) => node.expandable;
private _getChildren = (node: ThreadNode): ThreadNode[] =>
node.children || [];
}
<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>
export class TreeTableSimpleExample {
treeControl: FlatTreeControl<ThreadFlatNode>;
treeFlattener: DtTreeFlattener<ThreadNode, ThreadFlatNode>;
dataSource: DtTreeDataSource<ThreadNode, ThreadFlatNode>;
constructor() {
this.treeControl = new DtTreeControl<ThreadFlatNode>(
this._getLevel,
this._isExpandable,
);
this.treeFlattener = new DtTreeFlattener(
this.transformer,
this._getLevel,
this._isExpandable,
this._getChildren,
);
this.dataSource = new DtTreeDataSource(
this.treeControl,
this.treeFlattener,
);
this.dataSource.data = TESTDATA;
}
hasChild = (_: number, _nodeData: ThreadFlatNode) => _nodeData.expandable;
transformer = (node: ThreadNode, level: number): ThreadFlatNode => {
const flatNode = new ThreadFlatNode();
flatNode.name = node.name;
flatNode.level = level;
flatNode.expandable = !!node.children;
flatNode.blocked = node.blocked;
flatNode.running = node.running;
flatNode.waiting = node.waiting;
return flatNode;
};
private _getLevel = (node: ThreadFlatNode) => node.level;
private _isExpandable = (node: ThreadFlatNode) => node.expandable;
private _getChildren = (node: ThreadNode): ThreadNode[] =>
node.children || [];
}
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.
´
Loading interactive demo...
<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"
aria-label-close-button ="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 >
export class TreeTableProblemIndicatorExample {
treeControl: FlatTreeControl<ThreadFlatNode>;
treeFlattener: DtTreeFlattener<ThreadNode, ThreadFlatNode>;
dataSource: DtTreeDataSource<ThreadNode, ThreadFlatNode>;
constructor ( ) {
this .treeControl = new DtTreeControl<ThreadFlatNode>(
this ._getLevel,
this ._isExpandable,
);
this .treeFlattener = new DtTreeFlattener(
this .transformer,
this ._getLevel,
this ._isExpandable,
this ._getChildren,
);
this .dataSource = new DtTreeDataSource(
this .treeControl,
this .treeFlattener,
);
this .dataSource.data = TESTDATA;
}
hasChild = (_: number , _nodeData: ThreadFlatNode ) => _nodeData.expandable;
transformer = (node: ThreadNode, level: number ): ThreadFlatNode => {
const flatNode = new ThreadFlatNode();
flatNode.name = node.name;
flatNode.level = level;
flatNode.threadlevel = node.threadlevel;
flatNode.expandable = !!node.children;
flatNode.blocked = node.blocked;
flatNode.running = node.running;
flatNode.waiting = node.waiting;
flatNode.totalTimeConsumption = node.totalTimeConsumption;
flatNode.icon = node.icon;
return flatNode;
};
private _getLevel = (node: ThreadFlatNode ) => node.level;
private _isExpandable = (node: ThreadFlatNode ) => node.expandable;
private _getChildren = (node: ThreadNode): ThreadNode[] =>
node.children || [];
}
<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"
aria-label-close-button="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>
export class TreeTableProblemIndicatorExample {
treeControl: FlatTreeControl<ThreadFlatNode>;
treeFlattener: DtTreeFlattener<ThreadNode, ThreadFlatNode>;
dataSource: DtTreeDataSource<ThreadNode, ThreadFlatNode>;
constructor() {
this.treeControl = new DtTreeControl<ThreadFlatNode>(
this._getLevel,
this._isExpandable,
);
this.treeFlattener = new DtTreeFlattener(
this.transformer,
this._getLevel,
this._isExpandable,
this._getChildren,
);
this.dataSource = new DtTreeDataSource(
this.treeControl,
this.treeFlattener,
);
this.dataSource.data = TESTDATA;
}
hasChild = (_: number, _nodeData: ThreadFlatNode) => _nodeData.expandable;
transformer = (node: ThreadNode, level: number): ThreadFlatNode => {
const flatNode = new ThreadFlatNode();
flatNode.name = node.name;
flatNode.level = level;
flatNode.threadlevel = node.threadlevel;
flatNode.expandable = !!node.children;
flatNode.blocked = node.blocked;
flatNode.running = node.running;
flatNode.waiting = node.waiting;
flatNode.totalTimeConsumption = node.totalTimeConsumption;
flatNode.icon = node.icon;
return flatNode;
};
private _getLevel = (node: ThreadFlatNode) => node.level;
private _isExpandable = (node: ThreadFlatNode) => node.expandable;
private _getChildren = (node: ThreadNode): ThreadNode[] =>
node.children || [];
}
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
.
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
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.
´
Loading interactive demo...
<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 ="thread-progress"
[value ]="row.blocked"
[max ]="row.totalTimeConsumption"
> </dt-progress-bar >
<dt-progress-bar
class ="thread-progress"
[value ]="row.waiting"
[max ]="row.totalTimeConsumption"
> </dt-progress-bar >
<dt-progress-bar
class ="thread-progress"
[value ]="row.running"
[max ]="row.totalTimeConsumption"
> </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"
aria-label-close-button ="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 >
export class TreeTableDefaultExample {
treeControl: FlatTreeControl<ThreadFlatNode>;
treeFlattener: DtTreeFlattener<ThreadNode, ThreadFlatNode>;
dataSource: DtTreeDataSource<ThreadNode, ThreadFlatNode>;
constructor ( ) {
this .treeControl = new DtTreeControl<ThreadFlatNode>(
this ._getLevel,
this ._isExpandable,
);
this .treeFlattener = new DtTreeFlattener(
this .transformer,
this ._getLevel,
this ._isExpandable,
this ._getChildren,
);
this .dataSource = new DtTreeDataSource(
this .treeControl,
this .treeFlattener,
);
this .dataSource.data = TESTDATA;
}
hasChild = (_: number , _nodeData: ThreadFlatNode ) => _nodeData.expandable;
transformer = (node: ThreadNode, level: number ): ThreadFlatNode => {
const flatNode = new ThreadFlatNode();
flatNode.name = node.name;
flatNode.level = level;
flatNode.threadlevel = node.threadlevel;
flatNode.expandable = !!node.children;
flatNode.blocked = node.blocked;
flatNode.running = node.running;
flatNode.waiting = node.waiting;
flatNode.totalTimeConsumption = node.totalTimeConsumption;
flatNode.icon = node.icon;
return flatNode;
};
private _getLevel = (node: ThreadFlatNode ) => node.level;
private _isExpandable = (node: ThreadFlatNode ) => node.expandable;
private _getChildren = (node: ThreadNode): ThreadNode[] =>
node.children || [];
}
<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="thread-progress"
[value]="row.blocked"
[max]="row.totalTimeConsumption"
></dt-progress-bar>
<dt-progress-bar
class="thread-progress"
[value]="row.waiting"
[max]="row.totalTimeConsumption"
></dt-progress-bar>
<dt-progress-bar
class="thread-progress"
[value]="row.running"
[max]="row.totalTimeConsumption"
></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"
aria-label-close-button="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>
export class TreeTableDefaultExample {
treeControl: FlatTreeControl<ThreadFlatNode>;
treeFlattener: DtTreeFlattener<ThreadNode, ThreadFlatNode>;
dataSource: DtTreeDataSource<ThreadNode, ThreadFlatNode>;
constructor() {
this.treeControl = new DtTreeControl<ThreadFlatNode>(
this._getLevel,
this._isExpandable,
);
this.treeFlattener = new DtTreeFlattener(
this.transformer,
this._getLevel,
this._isExpandable,
this._getChildren,
);
this.dataSource = new DtTreeDataSource(
this.treeControl,
this.treeFlattener,
);
this.dataSource.data = TESTDATA;
}
hasChild = (_: number, _nodeData: ThreadFlatNode) => _nodeData.expandable;
transformer = (node: ThreadNode, level: number): ThreadFlatNode => {
const flatNode = new ThreadFlatNode();
flatNode.name = node.name;
flatNode.level = level;
flatNode.threadlevel = node.threadlevel;
flatNode.expandable = !!node.children;
flatNode.blocked = node.blocked;
flatNode.running = node.running;
flatNode.waiting = node.waiting;
flatNode.totalTimeConsumption = node.totalTimeConsumption;
flatNode.icon = node.icon;
return flatNode;
};
private _getLevel = (node: ThreadFlatNode) => node.level;
private _isExpandable = (node: ThreadFlatNode) => node.expandable;
private _getChildren = (node: ThreadNode): ThreadNode[] =>
node.children || [];
}
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.
´
Loading interactive demo...
<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"
> </dt-progress-bar >
<dt-progress-bar
class ="thread-progress"
[value ]="row.waiting"
[max ]="row.totalTimeConsumption"
> </dt-progress-bar >
<dt-progress-bar
class ="thread-progress"
[value ]="row.running"
[max ]="row.totalTimeConsumption"
> </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"
aria-label-close-button ="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 >
export class TreeTableAsyncShowMoreExample {
flatNodeMap = new Map<ThreadFlatNode, ThreadNode>();
nestedNodeMap = new Map<ThreadNode, ThreadFlatNode>();
treeControl: DtTreeControl<ThreadFlatNode>;
treeFlattener: DtTreeFlattener<ThreadNode, ThreadFlatNode>;
dataSource: DtTreeDataSource<ThreadNode, ThreadFlatNode>;
constructor ( ) {
this .treeControl = new DtTreeControl<ThreadFlatNode>(
this ._getLevel,
this ._isExpandable,
);
this .treeFlattener = new DtTreeFlattener(
this .transformer,
this ._getLevel,
this ._isExpandable,
this ._getChildren,
);
this .dataSource = new DtTreeDataSource(
this .treeControl,
this .treeFlattener,
);
this .dataSource.data = TESTDATA;
}
hasChild = (_: number , _nodeData: ThreadFlatNode ) => _nodeData.expandable;
transformer = (row: ThreadNode, level: number ): ThreadFlatNode => {
const existingNode = this .nestedNodeMap.get(row);
const flatNode =
existingNode && existingNode.name === row.name
? existingNode
: new ThreadFlatNode();
flatNode.name = row.name;
flatNode.level = level;
flatNode.threadlevel = row.threadlevel;
flatNode.expandable = !!row.children;
flatNode.blocked = row.blocked;
flatNode.running = row.running;
flatNode.waiting = row.waiting;
flatNode.totalTimeConsumption = row.totalTimeConsumption;
flatNode.icon = row.icon;
flatNode.isShowMore = row.isShowMore !== undefined ? row.isShowMore : false ;
this .flatNodeMap.set(flatNode, row);
this .nestedNodeMap.set(row, flatNode);
return flatNode;
};
private _getLevel = (node: ThreadFlatNode ) => node.level;
private _isExpandable = (node: ThreadFlatNode ) => node.expandable;
private _getChildren = (node: ThreadNode): ThreadNode[] =>
node.children || [];
isShowMore = (_: number , node: ThreadFlatNode ) => node.isShowMore;
showMore(): void {
TESTDATA[1 ].children!.pop();
TESTDATA[1 ].children!.push({
name: 'jetty-423' ,
icon: 'apache-tomcat' ,
threadlevel: 'S1' ,
totalTimeConsumption: 150 ,
waiting: 123 ,
running: 20 ,
blocked: 0 ,
});
TESTDATA[1 ].children!.push({
name: 'jetty-424' ,
icon: 'apache-tomcat' ,
threadlevel: 'S1' ,
totalTimeConsumption: 100 ,
waiting: 103 ,
running: 20 ,
blocked: 20 ,
});
this .dataSource.data = TESTDATA;
}
}
<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"
></dt-progress-bar>
<dt-progress-bar
class="thread-progress"
[value]="row.waiting"
[max]="row.totalTimeConsumption"
></dt-progress-bar>
<dt-progress-bar
class="thread-progress"
[value]="row.running"
[max]="row.totalTimeConsumption"
></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"
aria-label-close-button="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>
export class TreeTableAsyncShowMoreExample {
/** Map from flat node to nested node. This helps us finding the nested node to be modified */
flatNodeMap = new Map<ThreadFlatNode, ThreadNode>();
/** Map from nested node to flattened node. This helps us to keep the same object for selection */
nestedNodeMap = new Map<ThreadNode, ThreadFlatNode>();
treeControl: DtTreeControl<ThreadFlatNode>;
treeFlattener: DtTreeFlattener<ThreadNode, ThreadFlatNode>;
dataSource: DtTreeDataSource<ThreadNode, ThreadFlatNode>;
constructor() {
this.treeControl = new DtTreeControl<ThreadFlatNode>(
this._getLevel,
this._isExpandable,
);
this.treeFlattener = new DtTreeFlattener(
this.transformer,
this._getLevel,
this._isExpandable,
this._getChildren,
);
this.dataSource = new DtTreeDataSource(
this.treeControl,
this.treeFlattener,
);
this.dataSource.data = TESTDATA;
}
hasChild = (_: number, _nodeData: ThreadFlatNode) => _nodeData.expandable;
transformer = (row: ThreadNode, level: number): ThreadFlatNode => {
const existingNode = this.nestedNodeMap.get(row);
const flatNode =
existingNode && existingNode.name === row.name
? existingNode
: new ThreadFlatNode();
flatNode.name = row.name;
flatNode.level = level;
flatNode.threadlevel = row.threadlevel;
flatNode.expandable = !!row.children;
flatNode.blocked = row.blocked;
flatNode.running = row.running;
flatNode.waiting = row.waiting;
flatNode.totalTimeConsumption = row.totalTimeConsumption;
flatNode.icon = row.icon;
flatNode.isShowMore = row.isShowMore !== undefined ? row.isShowMore : false;
this.flatNodeMap.set(flatNode, row);
this.nestedNodeMap.set(row, flatNode);
return flatNode;
};
private _getLevel = (node: ThreadFlatNode) => node.level;
private _isExpandable = (node: ThreadFlatNode) => node.expandable;
private _getChildren = (node: ThreadNode): ThreadNode[] =>
node.children || [];
isShowMore = (_: number, node: ThreadFlatNode) => node.isShowMore;
showMore(): void {
// removing show more row
TESTDATA[1].children!.pop();
// adding data
TESTDATA[1].children!.push({
name: 'jetty-423',
icon: 'apache-tomcat',
threadlevel: 'S1',
totalTimeConsumption: 150,
waiting: 123,
running: 20,
blocked: 0,
});
TESTDATA[1].children!.push({
name: 'jetty-424',
icon: 'apache-tomcat',
threadlevel: 'S1',
totalTimeConsumption: 100,
waiting: 103,
running: 20,
blocked: 20,
});
this.dataSource.data = TESTDATA;
}
}
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).