The <dt-tree-table> provides the functionality of a tree displayed in a grid/table. The API is very similar to the <dt-table> component. You define the different columns and rows and choose which columns should be rendered inside each row.

It is also possible to have multiple different row templates that can be switched when a row should have a special behavior e.g. a show more row for lazy loading more rows.

In a tree-table it is never possible to sort for a column.

<dt-tree-table [dataSource]="dataSource" [treeControl]="treeControl">
  <ng-container dtColumnDef="name">
    <dt-tree-table-header-cell *dtHeaderCellDef>
      Name
    </dt-tree-table-header-cell>
    <dt-tree-table-toggle-cell *dtCellDef="let row">
      {{ row.name }}
    </dt-tree-table-toggle-cell>
  </ng-container>
  <ng-container dtColumnDef="blocked" dtColumnAlign="right">
    <dt-tree-table-header-cell *dtHeaderCellDef>
      Blocked
    </dt-tree-table-header-cell>
    <dt-cell *dtCellDef="let row">{{ row.blocked }}ms</dt-cell>
  </ng-container>
  <ng-container dtColumnDef="running" dtColumnAlign="center">
    <dt-tree-table-header-cell *dtHeaderCellDef>
      Running
    </dt-tree-table-header-cell>
    <dt-cell *dtCellDef="let row">{{ row.running }}ms</dt-cell>
  </ng-container>
  <ng-container dtColumnDef="waiting">
    <dt-tree-table-header-cell *dtHeaderCellDef>
      Waiting
    </dt-tree-table-header-cell>
    <dt-cell *dtCellDef="let row">{{ row.waiting }}ms</dt-cell>
  </ng-container>
  <dt-header-row
    *dtHeaderRowDef="['name', 'blocked', 'running', 'waiting']"
  ></dt-header-row>
  <dt-tree-table-row
    *dtRowDef="let row; columns: ['name', 'blocked', 'running', 'waiting']"
    [data]="row"
  ></dt-tree-table-row>
</dt-tree-table>

Imports

You have to import the DtTreeTableModule when you want to use the <dt-tree-table>.

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

Initialization

Start by adding the <dt-tree-table> component to your template and provide it with a datasource and a treecontrol.

<dt-tree-table
  [dataSource]="dataSource"
  [treeControl]="treeControl"
></dt-tree-table>

Provide the data

Since a table is a flat structure but we want to show hierachical tree data there is a special datasource called DtTreeDataSource involved. The DtTreeControl is responsible for controlling expanding/collapsing of nodes.

private _getLevel = (node: ThreadFlatNode) => node.level;

private _isExpandable = (node: ThreadFlatNode) => node.expandable;

this.treeControl = new DtTreeControl<ThreadFlatNode>(this._getLevel, this._isExpandable);

The DtTreeFlattener takes multiple accessor functions as parameters to transform the hierachical data to a flat datastructure that can be rendered inside the table. The DtTreeFlattener is a generic class that transforms a hierachical structure of nodes of type T to a flat structure of nodes of type F; In the example below it takes the hierachical type ThreadNode and transforms it to ThreadFlatNode.

private _transformer = (node: ThreadNode, level: number): ThreadFlatNode => {
  ...
  return flatNode;
}
private _getLevel = (node: ThreadFlatNode) => node.level;

private _isExpandable = (node: ThreadFlatNode) => node.expandable;

private _getChildren = (node: ThreadNode): ThreadNode[] => node.children || [];

this.treeFlattener =  new DtTreeFlattener<ThreadNode, ThreadFlatNode>(this._transformer, this._getLevel, this._isExpandable, this._getChildren);

The DtTreeDataSource gets the DtTreeFlattener and the DtTreeControl as parameters.

this.dataSource = new DtTreeDataSource(this.treeControl, this.treeFlattener);

By assigning the data property on the datasource with a new set of data the tree-table is populated with the rows.

this.dataSource.data = [
  {
    name: 'hz.hzInstance_1_cluster.thread',
    icon: 'apache-tomcat',
    threadlevel: 'S0',
    totalTimeConsumption: 150,
    waiting: 123,
    running: 20,
    blocked: 0,
    children: [
      {
        name: 'hz.hzInstance_1_cluster.thread_1_hz.hzInstance_1_cluster.thread-1',
        icon: 'apache-tomcat',
        threadlevel: 'S1',
        totalTimeConsumption: 150,
        waiting: 123,
        running: 20,
        blocked: 0,
      },
      {
        name: 'hz.hzInstance_1_cluster.thread-2',
        icon: 'apache-tomcat',
        threadlevel: 'S1',
        totalTimeConsumption: 150,
        waiting: 130,
        running: 0,
        blocked: 0,
      },
    ],
  },
];

Adding column templates

To specify the different columns that are shown inside the table you have to define column templates. Each column consists of <dt-tree-table-header-cell> and either a <dt-tree-table-toggle-cell> or a <dt-cell> inside a <ng-container>.

The first column shown in the <dt-tree-table> has to include the <dt-tree-table-toggle-cell>. This special cell includes the indentation logic and the toggle button used to expand/collapse the next level of rows.

<ng-container dtColumnDef="name">
  <dt-tree-table-header-cell *dtHeaderCellDef>Name</dt-tree-table-header-cell>
  <dt-tree-table-toggle-cell *dtCellDef="let row">
    {{row.name}}
  </dt-tree-table-toggle-cell>
</ng-container>

A normal column could like the following example

<ng-container dtColumnDef="waiting">
  <dt-tree-table-header-cell *dtHeaderCellDef>
    Waiting
  </dt-tree-table-header-cell>
  <dt-cell *dtCellDef="let row"> {{row.waiting}} </dt-cell>
</ng-container>

After adding all column definitions the next step is to add the row definitions.

Adding rows

You want to add the table header by adding a <dt-header-row> inside the <dt-tree-table> tag.

<dt-header-row
  *dtHeaderRowDef="['name', 'total', 'blocked', 'running', 'waiting', 'actions']"
></dt-header-row>

The next step is to add the row where the columns get rendered. You can do this by adding the <dt-tree-table-row> inside the <dt-tree-table> tag. You can specify the columns that should be rendered and don't forget to bind the row's data to the <dt-tree-table-row>s data input [data]="row".

<dt-tree-table-row
  *dtRowDef="let row; columns: ['name', 'total', 'blocked', 'running', 'waiting', 'actions'];"
  [data]="row"
></dt-tree-table-row>

Column alignment

If you want to specify an alignment for the content of a column you can do so by setting the dtColumnAlign input on the <ng-container dtColumnDef=... dtColumnAlign="text">

The dtColumnAlign input handles the following values: 'left' | 'right' | 'center' or one of the typed values that get mapped to left, right and center internally 'text' | 'id' | 'icon' | 'control' | 'number' | 'date' | 'ip'.

DtIndicator

You can use the DtIndicator directive the same way as in the <dt-table> to indicate a warning/error in a row. This will result in a Problem indicator being shown in front of the row.

<dt-tree-table [dataSource]="dataSource" [treeControl]="treeControl">
  <ng-container dtColumnDef="name">
    <dt-tree-table-header-cell *dtHeaderCellDef>
      Name
    </dt-tree-table-header-cell>
    <dt-tree-table-toggle-cell *dtCellDef="let row">
      <dt-info-group>
        <dt-info-group-icon>
          <dt-icon [name]="row.icon"></dt-icon>
        </dt-info-group-icon>
        <dt-info-group-title>{{ row.name }}</dt-info-group-title>
        {{ row.threadlevel }}
      </dt-info-group>
    </dt-tree-table-toggle-cell>
  </ng-container>
  <ng-container dtColumnDef="blocked" dtColumnAlign="right">
    <dt-tree-table-header-cell *dtHeaderCellDef>
      Blocked
    </dt-tree-table-header-cell>
    <dt-cell
      *dtCellDef="let row"
      [dtIndicator]="row.blocked > 0"
      dtIndicatorColor="error"
    >
      {{ row.blocked }}ms
    </dt-cell>
  </ng-container>
  <ng-container dtColumnDef="running" dtColumnAlign="center">
    <dt-tree-table-header-cell *dtHeaderCellDef>
      Running
    </dt-tree-table-header-cell>
    <dt-cell *dtCellDef="let row">{{ row.running }}ms</dt-cell>
  </ng-container>
  <ng-container dtColumnDef="waiting">
    <dt-tree-table-header-cell *dtHeaderCellDef>
      Waiting
    </dt-tree-table-header-cell>
    <dt-cell *dtCellDef="let row">{{ row.waiting }}ms</dt-cell>
  </ng-container>
  <ng-container dtColumnDef="actions">
    <dt-tree-table-header-cell *dtHeaderCellDef>
      Actions
    </dt-tree-table-header-cell>
    <dt-cell *dtCellDef="let row">
      <button dt-icon-button variant="nested" aria-label="Filter results">
        <dt-icon name="filter"></dt-icon>
      </button>
      <button
        dt-icon-button
        variant="nested"
        [dtContextDialogTrigger]="dialog"
        aria-label="Show more data"
      >
        <dt-icon name="more"></dt-icon>
      </button>
      <dt-context-dialog
        #dialog
        aria-label="Show more data"
        ariaLabelClose="Close context dialog"
      >
        {{ row.name }} context dialog
      </dt-context-dialog>
    </dt-cell>
  </ng-container>
  <dt-header-row
    *dtHeaderRowDef="['name', 'blocked', 'running', 'waiting', 'actions']"
  ></dt-header-row>
  <dt-tree-table-row
    *dtRowDef="
      let row;
      columns: ['name', 'blocked', 'running', 'waiting', 'actions']
    "
    [data]="row"
  ></dt-tree-table-row>
</dt-tree-table>

DtTreeTable inputs

Name Type Default Description
treeControl DtTreeControl<T> - Input for the treeControl that handles expand/collapse of rows
hasInteractiveRows boolean false Input wether the tree-table should have interactive rows - results in a hover effect
trackBy Fn(index, T) Tracking function that will be used to check the differences in data changes. Used similarly to ngFor trackBy function.

It is obligatory to provide either an aria-label or aria-labelledby.

DtTreeTableRow inputs

Name Type Description
data T The data for the row. Note that this might be removed rather soon and made obsolete due to a feature request on the underlying cdk table

DtTreeTableToggleCell inputs

Name Type Default Description
expanded boolean false Sets or gets the DtTreeTableToggleCell's expanded state.

DtTreeTableToggleCell outputs

Name Type Description
expandChange EventEmitter<boolean> Emits an event when the expanded state changes. The event parameter is set to true when it is expanded
expanded EventEmitter<void> Event emitted when the DtTreeTableToggleCell is expanded.
collapsed EventEmitter<void> Event emitted when the DtTreeTableToggleCell is collapsed.

DtTreeControl

Name Type Default Description
dataNodes T[] Saved row data for expandAll action
expansionModel SelectionModel<T> A selection model to track expansion status
getChildren (row: T) => (Observable<T[]> | T[] | undefined | null) Function that returns an observable containing the child rows, the child rows, undefined or null
getLevel (row: T) => number accessor fn for the level of the row
isExpandable (dataNode: T) => boolean function that returns wether a node is expandable

Methods

Name Return value Description
collapse(row: T) void collapses one single row
collapseAll void collapses all rows
collapseDescendants(row: T) void collapses all descendants of a row
expand(row: T) void expands one single row
expandAll void expands all rows
expandDescendants(row: T) void expands all descendants of a row
getDescendants(row: T) T[] Gets a list of descendant rows
isExpanded(row: T) boolean Wether a row is expanded
toggle(row: T) void Toggles a single row
toggleDescendants(row: T) void Toggles descendants of a row

DtTreeFlattener<T, F>

Parameters Type Description
getChildren (row: T) => (Observable<T[]> | T[] | undefined | null) function that returns an observable containing the child rows, the child rows, undefined or null
getLevel (row: T) => number accessor fn for the level of the row
isExpandable (row: T) => boolean function that returns wether a node is expandable
transformFunction (row: T, level: number) => F function that transforms from type T to flat type F

Tree table in use

Rows with multiple lines

Use the dt-info-group for rows with multiple lines of text and icons.

<dt-tree-table [dataSource]="dataSource" [treeControl]="treeControl">
  <ng-container dtColumnDef="name">
    <dt-tree-table-header-cell *dtHeaderCellDef>
      Name
    </dt-tree-table-header-cell>
    <dt-tree-table-toggle-cell *dtCellDef="let row">
      <dt-info-group>
        <dt-info-group-icon>
          <dt-icon [name]="row.icon"></dt-icon>
        </dt-info-group-icon>
        <dt-info-group-title>{{ row.name }}</dt-info-group-title>
        {{ row.threadlevel }}
      </dt-info-group>
    </dt-tree-table-toggle-cell>
  </ng-container>
  <ng-container dtColumnDef="total">
    <dt-tree-table-header-cell *dtHeaderCellDef>
      Total time consumption
    </dt-tree-table-header-cell>
    <dt-cell *dtCellDef="let row">
      <dt-progress-bar
        class="dt-thread-progress"
        [value]="row.blocked"
        [max]="row.totalTimeConsumption"
        aria-label="Blocked resources progress"
      ></dt-progress-bar>
      <dt-progress-bar
        class="dt-thread-progress"
        [value]="row.waiting"
        [max]="row.totalTimeConsumption"
        aria-label="Waiting resources progress"
      ></dt-progress-bar>
      <dt-progress-bar
        class="dt-thread-progress"
        [value]="row.running"
        [max]="row.totalTimeConsumption"
        aria-label="Running resources progress"
      ></dt-progress-bar>
    </dt-cell>
  </ng-container>
  <ng-container dtColumnDef="blocked" dtColumnAlign="right">
    <dt-tree-table-header-cell *dtHeaderCellDef>
      Blocked
    </dt-tree-table-header-cell>
    <dt-cell *dtCellDef="let row">{{ row.blocked }}ms</dt-cell>
  </ng-container>
  <ng-container dtColumnDef="running" dtColumnAlign="center">
    <dt-tree-table-header-cell *dtHeaderCellDef>
      Running
    </dt-tree-table-header-cell>
    <dt-cell *dtCellDef="let row">{{ row.running }}ms</dt-cell>
  </ng-container>
  <ng-container dtColumnDef="waiting">
    <dt-tree-table-header-cell *dtHeaderCellDef>
      Waiting
    </dt-tree-table-header-cell>
    <dt-cell *dtCellDef="let row">{{ row.waiting }}ms</dt-cell>
  </ng-container>
  <ng-container dtColumnDef="actions">
    <dt-tree-table-header-cell *dtHeaderCellDef>
      Actions
    </dt-tree-table-header-cell>
    <dt-cell *dtCellDef="let row">
      <button dt-icon-button variant="nested" aria-label="Filter results">
        <dt-icon name="filter"></dt-icon>
      </button>
      <button
        dt-icon-button
        variant="nested"
        [dtContextDialogTrigger]="dialog"
        aria-label="Show more data"
      >
        <dt-icon name="more"></dt-icon>
      </button>
      <dt-context-dialog
        #dialog
        aria-label="Show more data"
        ariaLabelClose="Close context dialog"
      >
        {{ row.name }} context dialog
      </dt-context-dialog>
    </dt-cell>
  </ng-container>
  <dt-header-row
    *dtHeaderRowDef="[
      'name',
      'total',
      'blocked',
      'running',
      'waiting',
      'actions'
    ]"
  ></dt-header-row>
  <dt-tree-table-row
    *dtRowDef="
      let row;
      columns: ['name', 'total', 'blocked', 'running', 'waiting', 'actions']
    "
    [data]="row"
  ></dt-tree-table-row>
</dt-tree-table>
<button dt-button (click)="treeControl.expandAll()">Expand all</button>
<button dt-button (click)="treeControl.collapseAll()">Collapse all</button>

Load entries asynchronously

In some cases there are too many hierarchy levels or too many entries in a tree, it's not possible to show them all at once. In this case one row can be used for an expand button which will load all entries at once.

<dt-tree-table [dataSource]="dataSource" [treeControl]="treeControl">
  <ng-container dtColumnDef="name">
    <dt-tree-table-header-cell *dtHeaderCellDef>
      Name
    </dt-tree-table-header-cell>
    <dt-tree-table-toggle-cell *dtCellDef="let row">
      <dt-info-group>
        <dt-info-group-icon>
          <dt-icon [name]="row.icon"></dt-icon>
        </dt-info-group-icon>
        <dt-info-group-title>{{ row.name }}</dt-info-group-title>
        {{ row.threadlevel }}
      </dt-info-group>
    </dt-tree-table-toggle-cell>
  </ng-container>
  <ng-container dtColumnDef="total">
    <dt-tree-table-header-cell *dtHeaderCellDef>
      Total time consumption
    </dt-tree-table-header-cell>
    <dt-cell *dtCellDef="let row">
      <ng-container *ngIf="!row.isShowMore">
        <dt-progress-bar
          class="thread-progress"
          [value]="row.blocked"
          [max]="row.totalTimeConsumption"
          aria-label="Blocked resources progress"
        ></dt-progress-bar>
        <dt-progress-bar
          class="thread-progress"
          [value]="row.waiting"
          [max]="row.totalTimeConsumption"
          aria-label="Waiting resources progress"
        ></dt-progress-bar>
        <dt-progress-bar
          class="thread-progress"
          [value]="row.running"
          [max]="row.totalTimeConsumption"
          aria-label="Running resources progress"
        ></dt-progress-bar>
      </ng-container>
    </dt-cell>
  </ng-container>
  <ng-container dtColumnDef="blocked" dtColumnAlign="right">
    <dt-tree-table-header-cell *dtHeaderCellDef>
      Blocked
    </dt-tree-table-header-cell>
    <dt-cell *dtCellDef="let row">
      <ng-container *ngIf="!row.isShowMore"> {{ row.blocked }}ms </ng-container>
    </dt-cell>
  </ng-container>
  <ng-container dtColumnDef="running" dtColumnAlign="center">
    <dt-tree-table-header-cell *dtHeaderCellDef>
      Running
    </dt-tree-table-header-cell>
    <dt-cell *dtCellDef="let row">
      <ng-container *ngIf="!row.isShowMore"> {{ row.running }}ms </ng-container>
    </dt-cell>
  </ng-container>
  <ng-container dtColumnDef="waiting">
    <dt-tree-table-header-cell *dtHeaderCellDef>
      Waiting
    </dt-tree-table-header-cell>
    <dt-cell *dtCellDef="let row">
      <ng-container *ngIf="!row.isShowMore"> {{ row.waiting }}ms </ng-container>
    </dt-cell>
  </ng-container>
  <ng-container dtColumnDef="showMore">
    <dt-tree-table-toggle-cell *dtCellDef="let row">
      <button dt-button variant="secondary" (click)="showMore()">
        Show more
      </button>
    </dt-tree-table-toggle-cell>
  </ng-container>
  <ng-container dtColumnDef="actions">
    <dt-tree-table-header-cell *dtHeaderCellDef>
      Actions
    </dt-tree-table-header-cell>
    <dt-cell *dtCellDef="let row">
      <ng-container *ngIf="!row.isShowMore">
        <button dt-icon-button variant="nested" aria-label="Filter results">
          <dt-icon name="filter"></dt-icon>
        </button>
        <button
          dt-icon-button
          variant="nested"
          [dtContextDialogTrigger]="dialog"
          aria-label="Show more data"
        >
          <dt-icon name="more"></dt-icon>
        </button>
        <dt-context-dialog
          #dialog
          aria-label="Show more data"
          ariaLabelClose="Close context dialog"
        >
          {{ row.name }} context dialog
        </dt-context-dialog>
      </ng-container>
    </dt-cell>
  </ng-container>
  <dt-header-row
    *dtHeaderRowDef="[
      'name',
      'total',
      'blocked',
      'running',
      'waiting',
      'actions'
    ]"
  ></dt-header-row>
  <dt-tree-table-row
    *dtRowDef="
      let row;
      columns: ['name', 'total', 'blocked', 'running', 'waiting', 'actions']
    "
    [data]="row"
  ></dt-tree-table-row>
  <dt-tree-table-row
    *dtRowDef="
      let row;
      columns: [
        'showMore',
        'total',
        'blocked',
        'running',
        'waiting',
        'actions'
      ];
      when: isShowMore
    "
    [data]="row"
  ></dt-tree-table-row>
</dt-tree-table>

Responsive behavior

As long as a screen is big enough the tree-table can show all desired columns. Once a screen width decreases columns disappear according to their importance for a specific use case. The least important column will disappear first. The only column always visible is the tree itself (name of entity).