TreeTable is used to display hierarchical data in tabular format.
import { TreeTableModule } from 'primeng/treetable';
TreeTable requires a collection of TreeNode instances as a value components as children for the representation.
<p-treetable [value]="files" [scrollable]="true" [tableStyle]="{'min-width':'50rem'}">
<ng-template #header>
<tr>
<th>Name</th>
<th>Size</th>
<th>Type</th>
</tr>
</ng-template>
<ng-template #body let-rowNode let-rowData="rowData">
<tr [ttRow]="rowNode">
<td>
<div class="flex items-center gap-2">
<p-treetable-toggler [rowNode]="rowNode" />
<span>{{ rowData.name }}</span>
</div>
</td>
<td>{{ rowData.size }}</td>
<td>{{ rowData.type }}</td>
</tr>
</ng-template>
</p-treetable>
Columns can be created programmatically.
<p-treetable [value]="files" [columns]="cols" [scrollable]="true" [tableStyle]="{'min-width':'50rem'}">
<ng-template #header let-columns>
<tr>
@for (col of columns; track col) {
<th>
{{ col.header }}
</th>
}
</tr>
</ng-template>
<ng-template #body let-rowNode let-rowData="rowData" let-columns="columns">
<tr [ttRow]="rowNode">
@for (col of columns; let first = $first; track col) {
<td>
@if (first) {
<div class="flex items-center gap-2">
<p-treetable-toggler [rowNode]="rowNode"></p-treetable-toggler>
<span>{{ rowData[col.field] }}</span>
</div>
} @else {
{{ rowData[col.field] }}
}
</td>
}
</tr>
</ng-template>
</p-treetable>
Expansion state is controlled with expandedKeys property.
<p-button (click)="toggleApplications()" label="Toggle Applications" class="block mb-4" />
<p-treetable [value]="files" [scrollable]="true" [tableStyle]="{ 'min-width': '50rem' }">
<ng-template #header>
<tr>
<th>Name</th>
<th>Size</th>
<th>Type</th>
</tr>
</ng-template>
<ng-template #body let-rowNode let-rowData="rowData">
<tr [ttRow]="rowNode">
<td>
<div class="flex items-center gap-2">
<p-treetable-toggler [rowNode]="rowNode" />
<span>{{ rowData.name }}</span>
</div>
</td>
<td>{{ rowData.size }}</td>
<td>{{ rowData.type }}</td>
</tr>
</ng-template>
</p-treetable>
Custom content at caption, header, body and summary sections are supported via templating.
<p-treetable [value]="files" [columns]="cols" [tableStyle]="{ 'min-width': '50rem' }">
<ng-template #caption><div class="text-xl font-bold">File Viewer</div> </ng-template>
<ng-template #header let-columns>
<tr>
@for (col of columns; let last = $last; track col) {
<th [class]="{ 'w-40': last }">
{{ col.header }}
</th>
}
</tr>
</ng-template>
<ng-template #body let-rowNode let-rowData="rowData" let-columns="columns">
<tr [ttRow]="rowNode">
@for (col of columns; let first = $first; let last = $last; track col) {
<td>
@if (first) {
<div class="flex items-center gap-2">
<p-treetable-toggler [rowNode]="rowNode" />
<span>{{ rowData[col.field] }}</span>
</div>
} @else if (last) {
<div class="flex flex-wrap gap-2">
<p-button icon="pi pi-search" rounded="true" severity="secondary" />
<p-button icon="pi pi-pencil" rounded="true" severity="secondary" />
</div>
} @else {
<span>{{ rowData[col.field] }}</span>
}
</td>
}
</tr>
</ng-template>
<ng-template #summary>
<div style="text-align:left">
<p-button icon="pi pi-refresh" label="Reload" severity="warn" />
</div>
</ng-template>
</p-treetable>
In addition to a regular treetable, alternatives with alternative sizes are available. Add p-treetable-sm class to reduce the size of treetable or p-treetable-lg to enlarge it.
<div class="flex justify-center mb-4">
<p-selectbutton [options]="sizes" [(ngModel)]="selectedSize" [multiple]="false" optionLabel="name" optionValue="class" />
</div>
<p-treetable [value]="files" [scrollable]="true" [tableStyle]="{'min-width':'50rem'}" [class]="selectedSize">
<ng-template #header>
<tr>
<th>Name</th>
<th>Size</th>
<th>Type</th>
</tr>
</ng-template>
<ng-template #body let-rowNode let-rowData="rowData">
<tr [ttRow]="rowNode">
<td>
<div class="flex items-center gap-2">
<p-treetable-toggler [rowNode]="rowNode" />
<span>{{ rowData.name }}</span>
</div>
</td>
<td>{{ rowData.size }}</td>
<td>{{ rowData.type }}</td>
</tr>
</ng-template>
</p-treetable>
Enabling showGridlines displays grid lines.
<p-treetable [value]="files" [scrollable]="true" showGridlines [tableStyle]="{ 'min-width': '50rem' }">
<ng-template #header>
<tr>
<th>Name</th>
<th>Size</th>
<th>Type</th>
</tr>
</ng-template>
<ng-template #body let-rowNode let-rowData="rowData">
<tr [ttRow]="rowNode">
<td>
<div class="flex items-center gap-2">
<p-treetable-toggler [rowNode]="rowNode" />
<span>{{ rowData.name }}</span>
</div>
</td>
<td>{{ rowData.size }}</td>
<td>{{ rowData.type }}</td>
</tr>
</ng-template>
</p-treetable>
The loading property displays a mask layer to indicate busy state. Use the paginator to display the mask.
<p-treetable [value]="files" [scrollable]="true" [tableStyle]="{ 'min-width': '50rem' }" [loading]="true">
<ng-template #header>
<tr>
<th>Name</th>
<th>Size</th>
<th>Type</th>
</tr>
</ng-template>
<ng-template #body let-rowNode let-rowData="rowData">
<tr [ttRow]="rowNode">
<td>
<div class="flex items-center gap-2">
<p-treetable-toggler [rowNode]="rowNode" />
<span>{{ rowData.name }}</span>
</div>
</td>
<td>{{ rowData.size }}</td>
<td>{{ rowData.type }}</td>
</tr>
</ng-template>
</p-treetable>
Skeleton component can be used as a placeholder during the loading process.
<p-treetable [value]="files()" [scrollable]="true" [tableStyle]="{ 'min-width': '50rem' }">
<ng-template #header>
<tr>
<th>Name</th>
<th>Size</th>
<th>Type</th>
</tr>
</ng-template>
<ng-template #body>
<tr>
<td><p-skeleton /></td>
<td><p-skeleton /></td>
<td><p-skeleton /></td>
</tr>
</ng-template>
</p-treetable>
Pagination is enabled by adding paginator property and defining rows per page.
<p-treetable [value]="files" [columns]="cols" [paginator]="true" [rows]="5" [rowsPerPageOptions]="[5, 10, 25]" [scrollable]="true" [tableStyle]="{'min-width':'50rem'}">
<ng-template #header let-columns>
<tr>
@for (col of columns; track col) {
<th>
{{ col.header }}
</th>
}
</tr>
</ng-template>
<ng-template #body let-rowNode let-rowData="rowData" let-columns="columns">
<tr [ttRow]="rowNode">
@for (col of columns; let first = $first; track col) {
<td>
@if (first) {
<div class="flex items-center gap-2">
<p-treetable-toggler [rowNode]="rowNode"></p-treetable-toggler>
<span>{{ rowData[col.field] }}</span>
</div>
} @else {
{{ rowData[col.field] }}
}
</td>
}
</tr>
</ng-template>
</p-treetable>
Paginator UI is customized using the paginatorleft and paginatorright property. Each element can also be customized further with your own UI to replace the default one, refer to the Paginator component for more information about the advanced customization options.
<p-treetable [value]="files" [columns]="cols" [paginator]="true" [rows]="10" [scrollable]="true" [tableStyle]="{'min-width':'50rem'}">
<ng-template #header let-columns>
<tr>
@for (col of columns; track col) {
<th>
{{ col.header }}
</th>
}
</tr>
</ng-template>
<ng-template #body let-rowNode let-rowData="rowData" let-columns="columns">
<tr [ttRow]="rowNode">
@for (col of columns; let first = $first; track col) {
<td>
@if (first) {
<div class="flex items-center gap-2">
<p-treetable-toggler [rowNode]="rowNode"></p-treetable-toggler>
<span>{{ rowData[col.field] }}</span>
</div>
} @else {
{{ rowData[col.field] }}
}
</td>
}
</tr>
</ng-template>
<ng-template #paginatorleft>
<p-button icon="pi pi-refresh" text />
</ng-template>
<ng-template #paginatorright>
<p-button icon="pi pi-download" text />
</ng-template>
</p-treetable>
Sorting on a column is enabled by adding the ttSortableColumn property.
<p-treetable [value]="files" [columns]="cols" [scrollable]="true" [tableStyle]="{ 'min-width': '50rem' }">
<ng-template #header let-columns>
<tr>
@for (col of columns; track col) {
<th [ttSortableColumn]="col.field">
<div class="flex items-center gap-2">
{{ col.header }}
<p-treetable-sort-icon [field]="col.field" />
</div>
</th>
}
</tr>
</ng-template>
<ng-template #body let-rowNode let-rowData="rowData" let-columns="columns">
<tr [ttRow]="rowNode">
@for (col of columns; let first = $first; track col) {
<td>
@if (first) {
<div class="flex items-center gap-2">
<p-treetable-toggler [rowNode]="rowNode"></p-treetable-toggler>
<span>{{ rowData[col.field] }}</span>
</div>
} @else {
{{ rowData[col.field] }}
}
</td>
}
</tr>
</ng-template>
</p-treetable>
Multiple columns can be sorted by defining sortMode as multiple. This mode requires metaKey (e.g. ⌘) to be pressed when clicking a header.
<p-treetable [value]="files" [columns]="cols" sortMode="multiple" [scrollable]="true" [tableStyle]="{ 'min-width': '50rem' }">
<ng-template #header let-columns>
<tr>
@for (col of columns; track col) {
<th [ttSortableColumn]="col.field">
<div class="flex items-center gap-2">
{{ col.header }}
<p-treetable-sort-icon [field]="col.field" />
</div>
</th>
}
</tr>
</ng-template>
<ng-template #body let-rowNode let-rowData="rowData" let-columns="columns">
<tr [ttRow]="rowNode">
@for (col of columns; let first = $first; track col) {
<td>
@if (first) {
<div class="flex items-center gap-2">
<p-treetable-toggler [rowNode]="rowNode"></p-treetable-toggler>
<span>{{ rowData[col.field] }}</span>
</div>
} @else {
{{ rowData[col.field] }}
}
</td>
}
</tr>
</ng-template>
</p-treetable>
The filterMode specifies the filtering strategy, in lenient mode when the query matches a node, children of the node are not searched further as all descendants of the node are included. On the other hand, in strict mode when the query matches a node, filtering continues on all descendants. A general filled called filterGlobal is also provided to search all columns that support filtering.
<p-selectbutton [options]="filterModes" [(ngModel)]="filterMode" optionLabel="label" optionValue="value" />
<p-treetable #tt [value]="files" [columns]="cols" [filterMode]="filterMode" [scrollable]="true" [tableStyle]="{ 'min-width': '50rem' }">
<ng-template #caption>
<div class="flex justify-end items-center">
<p-iconfield>
<p-inputicon class="pi pi-search" />
<input type="text" pInputText placeholder="Global Search" (input)="tt.filterGlobal($event.target.value, 'contains')" />
</p-iconfield>
</div>
</ng-template>
<ng-template #header let-columns>
<tr>
@for (col of columns; track col) {
<th>
{{ col.header }}
</th>
}
</tr>
<tr>
@for (col of columns; track col) {
<th>
<input pInputText [placeholder]="'Filter by ' + col.field" type="text" (input)="tt.filter($event.target.value, col.field, col.filterMatchMode)" />
</th>
}
</tr>
</ng-template>
<ng-template #body let-rowNode let-rowData="rowData">
<tr [ttRow]="rowNode">
@for (col of cols; let first = $first; track col) {
<td>
@if (first) {
<div class="flex items-center gap-2">
<p-treetable-toggler [rowNode]="rowNode"></p-treetable-toggler>
<span>{{ rowData[col.field] }}</span>
</div>
} @else {
{{ rowData[col.field] }}
}
</td>
}
</tr>
</ng-template>
<ng-template #emptymessage>
<tr>
<td [attr.colspan]="cols?.length">No data found.</td>
</tr>
</ng-template>
</p-treetable>
Single node selection is configured by setting selectionMode as single along with selection properties to manage the selection value binding.
By default, metaKey press (e.g. ⌘) is necessary to unselect a node however this can be configured with disabling the metaKeySelection property. In touch enabled devices this option has no effect and behavior is same as setting it to false
<p-treetable [value]="files" [columns]="cols" selectionMode="single" [(selection)]="selectedNode" dataKey="name" [scrollable]="true" [tableStyle]="{'min-width':'50rem'}">
<ng-template #header let-columns>
<tr>
@for (col of columns; track col) {
<th>
{{ col.header }}
</th>
}
</tr>
</ng-template>
<ng-template #body let-rowNode let-rowData="rowData" let-columns="columns">
<tr [ttRow]="rowNode" [ttSelectableRow]="rowNode">
@for (col of columns; let first = $first; track col) {
<td>
@if (first) {
<div class="flex items-center gap-2">
<p-treetable-toggler [rowNode]="rowNode"></p-treetable-toggler>
<span>{{ rowData[col.field] }}</span>
</div>
} @else {
{{ rowData[col.field] }}
}
</td>
}
</tr>
</ng-template>
</p-treetable>
More than one node is selectable by setting selectionMode to multiple. By default in multiple selection mode, metaKey press (e.g. ⌘) is necessary to add to existing selections however this can be configured with disabling the metaKeySelection property. Note that in touch enabled devices, TreeTable always ignores metaKey.
<p-toggleswitch [(ngModel)]="metaKeySelection" />
<p-treetable [value]="files" [columns]="cols" selectionMode="multiple" [(selection)]="selectedNodes" dataKey="name" [metaKeySelection]="metaKeySelection" [scrollable]="true" [tableStyle]="{'min-width':'50rem'}">
<ng-template #header let-columns>
<tr>
@for (col of columns; track col) {
<th>
{{ col.header }}
</th>
}
</tr>
</ng-template>
<ng-template #body let-rowNode let-rowData="rowData" let-columns="columns">
<tr [ttRow]="rowNode" [ttSelectableRow]="rowNode">
@for (col of columns; let first = $first; track col) {
<td>
@if (first) {
<div class="flex items-center gap-2">
<p-treetable-toggler [rowNode]="rowNode"></p-treetable-toggler>
<span>{{ rowData[col.field] }}</span>
</div>
} @else {
{{ rowData[col.field] }}
}
</td>
}
</tr>
</ng-template>
</p-treetable>
Selection of multiple nodes via checkboxes is enabled by configuring selectionMode as checkbox.
In checkbox selection mode, value binding should be a key-value pair where key (or the dataKey) is the node key and value is an object that has checked and partialChecked properties to represent the checked state of a node.
{
'0': {
partialChecked: true
},
'0-0': {
partialChecked: false,
checked: true
},
'0-0-0': {
checked: true
},
'0-0-1': {
checked: true
},
'0-0-2': {
checked: true
}
}
<p-treetable [value]="files" [columns]="cols" selectionMode="checkbox" [(selectionKeys)]="selectionKeys" dataKey="key" [scrollable]="true" [tableStyle]="{ 'min-width': '50rem' }">
<ng-template #header let-columns>
<tr>
@for (col of columns; track col) {
<th>
{{ col.header }}
</th>
}
</tr>
</ng-template>
<ng-template #body let-rowNode let-rowData="rowData" let-columns="columns">
<tr [ttRow]="rowNode" [ttSelectableRow]="rowNode">
@for (col of columns; let first = $first; track col) {
<td>
@if (first) {
<div class="flex items-center gap-2">
<p-treetable-toggler [rowNode]="rowNode" />
<p-treetable-checkbox [value]="rowNode" />
<span>{{ rowData[col.field] }}</span>
</div>
} @else {
{{ rowData[col.field] }}
}
</td>
}
</tr>
</ng-template>
</p-treetable>
TreeTable provides onNodeSelect and onNodeUnselect events to listen selection events.
<p-treetable
[value]="files"
[columns]="cols"
selectionMode="single"
[(selection)]="selectedNode"
dataKey="name"
(onNodeSelect)="nodeSelect($event)"
(onNodeUnselect)="nodeUnselect($event)"
[scrollable]="true"
[tableStyle]="{ 'min-width': '50rem' }"
>
<ng-template #header let-columns>
<tr>
@for (col of columns; track col) {
<th>
{{ col.header }}
</th>
}
</tr>
</ng-template>
<ng-template #body let-rowNode let-rowData="rowData" let-columns="columns">
<tr [ttRow]="rowNode" [ttSelectableRow]="rowNode">
@for (col of columns; let first = $first; track col) {
<td>
@if (first) {
<div class="flex items-center gap-2">
<p-treetable-toggler [rowNode]="rowNode"></p-treetable-toggler>
<span>{{ rowData[col.field] }}</span>
</div>
} @else {
{{ rowData[col.field] }}
}
</td>
}
</tr>
</ng-template>
</p-treetable>
<p-treetable [value]="sales" [scrollable]="true" [tableStyle]="{'min-width':'50rem'}">
<ng-template #header>
<tr>
<th rowspan="3">Brand</th>
<th colspan="4">Sale Rate</th>
</tr>
<tr>
<th colspan="2">Sales</th>
<th colspan="2">Profits</th>
</tr>
<tr>
<th>Last Year</th>
<th>This Year</th>
<th>Last Year</th>
<th>This Year</th>
</tr>
</ng-template>
<ng-template #body let-rowNode let-rowData="rowData">
<tr>
<td>
<div class="flex items-center gap-2">
<p-treetable-toggler [rowNode]="rowNode" />
<span>{{ rowData.brand }}</span>
</div>
</td>
<td>{{ rowData.lastYearSale }}</td>
<td>{{ rowData.thisYearSale }}</td>
<td>{{ rowData.lastYearProfit }}</td>
<td>{{ rowData.thisYearProfit }}</td>
</tr>
</ng-template>
<ng-template #footer>
<tr>
<td colspan="3">Totals</td>
<td>$3,283,772</td>
<td>$2,126,925</td>
</tr>
</ng-template>
</p-treetable>
Lazy mode is handy to deal with large datasets, instead of loading the entire data, small chunks of data is loaded by invoking corresponding callbacks everytime paging, sorting and filtering occurs. Sample below imitates lazy loading data from a remote datasource using an in-memory list and timeouts to mimic network connection.
Enabling the lazy property and assigning the logical number of rows to totalRecords by doing a projection query are the key elements of the implementation so that paginator displays the UI assuming there are actually records of totalRecords size although in reality they are not present on page, only the records that are displayed on the current page exist.
In addition, only the root elements should be loaded, children can be loaded on demand using onNodeExpand callback.
| Name | Size | Type |
|---|
<p-treetable
[value]="files"
[columns]="cols"
[paginator]="true"
[rows]="10"
[lazy]="true"
(onLazyLoad)="loadNodes($event)"
[totalRecords]="1000"
[loading]="loading"
(onNodeExpand)="onNodeExpand($event)"
[scrollable]="true"
[tableStyle]="{ 'min-width': '50rem' }"
>
<ng-template #header let-columns>
<tr>
@for (col of columns; track col) {
<th>
{{ col.header }}
</th>
}
</tr>
</ng-template>
<ng-template #body let-rowNode let-rowData="rowData" let-columns="columns">
<tr [ttRow]="rowNode">
@for (col of columns; let first = $first; track col) {
<td>
@if (first) {
<div class="flex items-center gap-2">
<p-treetable-toggler [rowNode]="rowNode"></p-treetable-toggler>
<span>{{ rowData[col.field] }}</span>
</div>
} @else {
{{ rowData[col.field] }}
}
</td>
}
</tr>
</ng-template>
</p-treetable>
Adding scrollable property along with a scrollHeight for the data viewport enables vertical scrolling with fixed headers.
<p-treetable [value]="files" [columns]="cols" [scrollable]="true" scrollHeight="200px" [scrollable]="true" [tableStyle]="{ 'min-width': '50rem' }">
<ng-template #header let-columns>
<tr>
@for (col of columns; track col) {
<th>
{{ col.header }}
</th>
}
</tr>
</ng-template>
<ng-template #body let-rowNode let-rowData="rowData" let-columns="columns">
<tr [ttRow]="rowNode">
@for (col of columns; let first = $first; track col) {
<td>
@if (first) {
<div class="flex items-center gap-2">
<p-treetable-toggler [rowNode]="rowNode"></p-treetable-toggler>
<span>{{ rowData[col.field] }}</span>
</div>
} @else {
{{ rowData[col.field] }}
}
</td>
}
</tr>
</ng-template>
</p-treetable>
Flex scroll feature makes the scrollable viewport section dynamic instead of a fixed value so that it can grow or shrink relative to the parent size of the table. Click the button below to display a maximizable Dialog where data viewport adjusts itself according to the size changes.
<p-button label="Show" icon="pi pi-external-link" (onClick)="dialogVisible = true" />
<p-dialog [(visible)]="dialogVisible" header="Flex Scroll" [style]="{ width: '75vw' }" maximizable modal [contentStyle]="{ height: '300px' }">
<ng-template #content>
<p-treetable [value]="files" [scrollable]="true" scrollHeight="flex" [tableStyle]="{ 'min-width': '50rem' }">
<ng-template #header>
<tr>
<th>Name</th>
<th>Size</th>
<th>Type</th>
</tr>
</ng-template>
<ng-template #body let-rowNode let-rowData="rowData">
<tr [ttRow]="rowNode">
<td>
<div class="flex items-center gap-2">
<p-treetable-toggler [rowNode]="rowNode" />
<span>{{ rowData.name }}</span>
</div>
</td>
<td>{{ rowData.size }}</td>
<td>{{ rowData.type }}</td>
</tr>
</ng-template>
</p-treetable>
</ng-template>
<ng-template #footer>
<p-button label="Ok" icon="pi pi-check" (onClick)="dialogVisible = false" />
</ng-template>
</p-dialog>
Horizontal scrolling is enabled when the total width of columns exceeds table width.
<p-treetable [value]="files" [columns]="cols" [scrollable]="true" scrollHeight="250px" [scrollable]="true" [tableStyle]="{'min-width':'50rem'}">
<ng-template #colgroup let-columns>
<colgroup>
@for (col of columns; track col) {
<col style="width:500px" />
}
</colgroup>
</ng-template>
<ng-template #header let-columns>
<tr>
@for (col of columns; track col) {
<th>
{{ col.header }}
</th>
}
</tr>
</ng-template>
<ng-template #body let-rowNode let-rowData="rowData" let-columns="columns">
<tr [ttRow]="rowNode">
@for (col of columns; let first = $first; track col) {
<td>
@if (first) {
<div class="flex items-center gap-2">
<p-treetable-toggler [rowNode]="rowNode"></p-treetable-toggler>
<span>{{ rowData[col.field] }}</span>
</div>
} @else {
{{ rowData[col.field] }}
}
</td>
}
</tr>
</ng-template>
</p-treetable>
A column can be fixed during horizontal scrolling by enabling the frozenColumns property.
<p-treetable [value]="files" [columns]="scrollableCols" [frozenColumns]="frozenCols" [scrollable]="true" scrollHeight="250px" frozenWidth="200px" [scrollable]="true" [tableStyle]="{ 'min-width': '50rem' }">
<ng-template #colgroup let-columns>
<colgroup>
@for (col of columns; track col) {
<col style="width:250px" />
}
</colgroup>
</ng-template>
<ng-template #header let-columns>
<tr>
@for (col of columns; track col) {
<th>
{{ col.header }}
</th>
}
</tr>
</ng-template>
<ng-template #body let-rowData="rowData" let-columns="columns">
<tr [ttRow]="rowNode" style="height: 57px">
@for (col of columns; track col) {
<td>
{{ rowData[col.field] }}
</td>
}
</tr>
</ng-template>
<ng-template #frozenbody let-rowNode let-rowData="rowData">
<tr [ttRow]="rowNode" style="height: 57px">
<td>
<div class="flex items-center gap-2">
<p-treetable-toggler [rowNode]="rowNode" />
<span class="font-bold">{{ rowData.name }}</span>
</div>
</td>
</tr>
</ng-template>
</p-treetable>
Columns can be resized with drag and drop when resizableColumns is enabled. Default resize mode is fit that does not change the overall table width.
<p-treetable [value]="files" [columns]="cols" [resizableColumns]="true" [tableStyle]="{'min-width': '50rem'}" showGridlines>
<ng-template #header let-columns>
<tr>
@for (col of columns; track col) {
<th ttResizableColumn>
{{ col.header }}
</th>
}
</tr>
</ng-template>
<ng-template #body let-rowNode let-rowData="rowData" let-columns="columns">
<tr [ttRow]="rowNode">
@for (col of columns; let first = $first; track col) {
<td>
@if (first) {
<div class="flex items-center gap-2">
<p-treetable-toggler [rowNode]="rowNode"></p-treetable-toggler>
<span>{{ rowData[col.field] }}</span>
</div>
} @else {
{{ rowData[col.field] }}
}
</td>
}
</tr>
</ng-template>
</p-treetable>
Setting columnResizeMode as expand changes the table width as well.
<p-treetable [value]="files" [columns]="cols" [resizableColumns]="true" columnResizeMode="expand" showGridlines>
<ng-template #header let-columns>
<tr>
@for (col of columns; track col) {
<th ttResizableColumn>
{{ col.header }}
</th>
}
</tr>
</ng-template>
<ng-template #body let-rowNode let-rowData="rowData" let-columns="columns">
<tr [ttRow]="rowNode">
@for (col of columns; let first = $first; track col) {
<td>
@if (first) {
<div class="flex items-center gap-2">
<p-treetable-toggler [rowNode]="rowNode"></p-treetable-toggler>
<span>{{ rowData[col.field] }}</span>
</div>
} @else {
{{ rowData[col.field] }}
}
</td>
}
</tr>
</ng-template>
</p-treetable>
To utilize the column resize modes with a scrollable TreeTable, a colgroup template must be defined. The default value of scrollHeight is "flex," it can also be set as a string value.
<p-treetable [value]="files" [columns]="cols" [resizableColumns]="true" [scrollable]="true" scrollHeight="200px" showGridlines>
<ng-template #colgroup let-columns>
<colgroup>
@for (col of columns; track col) {
<col />
}
</colgroup>
</ng-template>
<ng-template #header let-columns>
<tr>
@for (col of columns; track col) {
<th ttResizableColumn>
{{ col.header }}
</th>
}
</tr>
</ng-template>
<ng-template #body let-rowNode let-rowData="rowData" let-columns="columns">
<tr [ttRow]="rowNode">
@for (col of columns; let first = $first; track col) {
<td>
@if (first) {
<div class="flex items-center gap-2">
<p-treetable-toggler [rowNode]="rowNode"></p-treetable-toggler>
<span>{{ rowData[col.field] }}</span>
</div>
} @else {
{{ rowData[col.field] }}
}
</td>
}
</tr>
</ng-template>
</p-treetable>
Order of the columns can be changed using drag and drop when reorderableColumns is present.
<p-treetable [value]="files" [columns]="cols" [reorderableColumns]="true" [scrollable]="true" [tableStyle]="{'min-width':'50rem'}">
<ng-template #header let-columns>
<tr>
@for (col of columns; track col) {
<th ttReorderableColumn>
{{ col.header }}
</th>
}
</tr>
</ng-template>
<ng-template #body let-rowNode let-rowData="rowData" let-columns="columns">
<tr [ttRow]="rowNode">
@for (col of columns; let first = $first; track col) {
<td>
@if (first) {
<div class="flex items-center gap-2">
<p-treetable-toggler [rowNode]="rowNode"></p-treetable-toggler>
<span>{{ rowData[col.field] }}</span>
</div>
} @else {
{{ rowData[col.field] }}
}
</td>
}
</tr>
</ng-template>
</p-treetable>
Column visibility based on a condition can be implemented with dynamic columns, in this sample a MultiSelect is used to manage the visible columns.
<p-treetable [value]="files" [columns]="selectedColumns" [scrollable]="true" [tableStyle]="{ 'min-width': '50rem' }">
<ng-template #caption>
<div style="text-align:left">
<p-multiselect [options]="cols" [(ngModel)]="selectedColumns" optionLabel="header" selectedItemsLabel="{0} columns selected" [style]="{ width: '20em' }" placeholder="Choose Columns" display="chip" />
</div>
</ng-template>
<ng-template #header let-columns>
<tr>
@for (col of columns; track col) {
<th>
{{ col.header }}
</th>
}
</tr>
</ng-template>
<ng-template #body let-rowNode let-rowData="rowData" let-columns="columns">
<tr [ttRow]="rowNode">
@for (col of columns; let first = $first; track col) {
<td>
@if (first) {
<div class="flex items-center gap-2">
<p-treetable-toggler [rowNode]="rowNode"></p-treetable-toggler>
<span>{{ rowData[col.field] }}</span>
</div>
} @else {
{{ rowData[col.field] }}
}
</td>
}
</tr>
</ng-template>
</p-treetable>
Particular rows and cells can be styled based on conditions. The ngClass receives a row data as a parameter to return a style class for a row whereas cells are customized using the body template.
<p-treetable [value]="files" [columns]="cols" [scrollable]="true" [tableStyle]="{ 'min-width': '50rem' }">
<ng-template #header let-columns>
<tr>
@for (col of columns; track col) {
<th>
{{ col.header }}
</th>
}
</tr>
</ng-template>
<ng-template #body let-rowNode let-rowData="rowData" let-columns="columns">
<tr [ttRow]="rowNode" [ngClass]="{ '!bg-surface-100 dark:!bg-surface-800': rowData.size.endsWith('kb') }">
@for (col of columns; let first = $first; track col) {
<td [class]="{ 'line-through': col.field === 'size' && rowData.size.endsWith('kb') }">
@if (first) {
<div class="flex items-center gap-2">
<p-treetable-toggler [rowNode]="rowNode"></p-treetable-toggler>
<span>{{ rowData[col.field] }}</span>
</div>
} @else {
{{ rowData[col.field] }}
}
</td>
}
</tr>
</ng-template>
</p-treetable>
TreeTable has exclusive integration with contextmenu component. In order to attach a menu to a table, add ttContextMenuRow directive to the rows that can be selected with context menu, define a local template variable for the menu and bind it to the contextMenu property of the table. This enables displaying the menu whenever a row is right clicked. A separate contextMenuSelection property is used to get a hold of the right clicked row. For dynamic columns, setting ttContextMenuRowDisabled property as true disables context menu for that particular row.
<p-toast [style]="{ marginTop: '80px' }" />
<p-treetable [value]="files" [columns]="cols" dataKey="name" [(contextMenuSelection)]="selectedNode" [contextMenu]="cm" [scrollable]="true" [tableStyle]="{ 'min-width': '50rem' }">
<ng-template #header let-columns>
<tr>
@for (col of columns; track col) {
<th>
{{ col.header }}
</th>
}
</tr>
</ng-template>
<ng-template #body let-rowNode let-rowData="rowData" let-columns="columns">
<tr [ttRow]="rowNode" [ttContextMenuRow]="rowNode">
@for (col of columns; let first = $first; track col) {
<td>
@if (first) {
<div class="flex items-center gap-2">
<p-treetable-toggler [rowNode]="rowNode"></p-treetable-toggler>
<span>{{ rowData[col.field] }}</span>
</div>
} @else {
{{ rowData[col.field] }}
}
</td>
}
</tr>
</ng-template>
</p-treetable>
<p-contextmenu #cm [model]="items" />
Default role of the table is table. Header, body and footer elements use rowgroup, rows use row role, header cells have columnheader and body cells use cell roles. Sortable headers utilizer aria-sort attribute either set to "ascending" or "descending".
Row elements manage aria-expanded for state and aria-level attribute to define the hierachy by ttRow directive. Table rows and table cells should be specified by users using the aria-posinset, aria-setsize, aria-label, and aria-describedby attributes, as they are determined through templating.
When selection is enabled, ttSelectableRow directive sets aria-selected to true on a row. In checkbox mode, the built-in checkbox component use checkbox role with aria-checked state attribute.
Editable cells use custom templating so you need to manage aria roles and attributes manually if required.
Paginator is a standalone component used inside the TreeTable, refer to the paginator for more information about the accessibility features.
| Key | Function |
|---|---|
| tab | Moves through the headers. |
| enter | Sorts the column. |
| space | Sorts the column. |
| Key | Function |
|---|---|
| tab | Moves focus to the first selected node when focus enters the component, if there is none then first element receives the focus. If focus is already inside the component, moves focus to the next focusable element in the page tab sequence. |
| shift + tab | Moves focus to the last selected node when focus enters the component, if there is none then first element receives the focus. If focus is already inside the component, moves focus to the previous focusable element in the page tab sequence. |
| enter | Selects the focused treenode. |
| space | Selects the focused treenode. |
| down arrow | Moves focus to the next treenode. |
| up arrow | Moves focus to the previous treenode. |
| right arrow | If node is closed, opens the node otherwise moves focus to the first child node. |
| left arrow | If node is open, closes the node otherwise moves focus to the parent node. |