Tree-table

The tree-table is used to show a tree of entities with additional data in a grid/table, hence with table headers. 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 || []; }

The tree-table combines the hierarchy of a tree with the basic functionality of a table.

Behavior

The only behavior of a tree-table is the functionality of expanding and collapsing rows. This is indicated by the arrow icon in a nested button, placed in front of each row. This icon points to the right if the row is collapsed, and points down once the row is expanded. These buttons are only visible for expandable tree rows.

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).

Problem state indication

Use the indicator functionality for showing problematic entries in the tree-table.

´ 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 || []; }

Corner cases

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 { /** 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; } } <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; } }

Imports

You have to import the DtTreeTableModule when you want to use the

<dt-tree-table>:

@NgModule({
  imports: [DtTreeTableModule],
})
class MyModule {}

Description

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.

Usage

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 || []; }

Options & Properties & Methods

DtTreeTable

Inputs

Name Type Default Description
treeControl DtTreeControl<T> - Input for the treeControl that handles expand/collapse of rows
ariaLabel string '' Input for the aria label of the treetable
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.

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

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

Advanced usecases

You can load children async if needed.

´ 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 { /** 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; } } <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; } }

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 || []; }