DevExtreme v26.1 is now available.

Explore our newest features/capabilities and share your thoughts with us.

Your search did not match any results.

Angular Tree View - Selection and Customization

The DevExtreme TreeView allows users to select individual tree nodes (on click or using checkboxes). In this demo, selected items appear as a flat (non-hierarchical) List.

Backend API
<div class="form"> <h4>Employees</h4> <dx-tree-view id="treeview" [items]="employees" [width]="340" [height]="320" [showCheckBoxesMode]="checkboxVisibility" [selectionMode]="selectionMode" [disabledNodeSelectionMode]="disabledNodeSelectionMode" [selectNodesRecursive]="recursiveSelection" [selectByClick]="selectOnClick" (onSelectionChanged)="treeViewSelectionChanged($event)" (onContentReady)="treeViewContentReady($event)" > <div *dxTemplate="let item of 'item'"> {{ item.fullName + " (" + item.position + ")" }} </div> </dx-tree-view> <div class="selected-container"> Selected employees <dx-list id="selected-employees" [width]="400" [height]="200" [items]="selectedEmployees" showScrollbar="always" > <div *dxTemplate="let item of 'item'"> {{ item.prefix + " " + item.fullName + " (" + item.position + ")" }} </div> </dx-list> </div> </div> <div class="options"> <div class="caption">Options</div> <div class="options-container"> <div class="options-section"> <div class="option"> <span>Checkbox Visibility:</span> <div class="editor-container"> <dx-select-box [items]="checkboxVisibilityOptions" [inputAttr]="{ 'aria-label': 'Checkbox Visibility' }" [value]="checkboxVisibility" (onValueChanged)="checkboxVisibilityValueChanged($event)" ></dx-select-box> </div> </div> <div class="option"> <span>Selection Mode:</span> <div class="editor-container"> <dx-select-box [items]="selectionModes" [value]="selectionMode" [inputAttr]="{ 'aria-label': 'Selection Mode' }" [disabled]="isSelectionModeDisabled" (onValueChanged)="selectionModeValueChanged($event)" ></dx-select-box> </div> </div> <div class="option"> <span>Disabled Node Selection Mode:</span> <div class="editor-container"> <dx-select-box [items]="disabledNodeSelectionModes" [value]="disabledNodeSelectionMode" [inputAttr]="{ 'aria-label': 'Disabled Node Selection Mode' }" (onValueChanged)="disabledNodeSelectionModeValueChanged($event)" ></dx-select-box> </div> </div> </div> <div class="options-section"> <div class="option"> <div class="caption-placeholder">&nbsp;</div> <div class="editor-container"> <dx-check-box text="Recursive Selection" [disabled]="isRecursiveDisabled" [(value)]="recursiveSelection" ></dx-check-box> </div> </div> <div class="option"> <div class="caption-placeholder">&nbsp;</div> <div class="editor-container"> <dx-check-box text="Select on Click" [(value)]="selectOnClick" ></dx-check-box> </div> </div> </div> </div> </div>
import { AfterViewInit, Component, Pipe, PipeTransform, enableProdMode, ViewChild, provideZoneChangeDetection, ChangeDetectorRef, } from '@angular/core'; import { bootstrapApplication } from '@angular/platform-browser'; import { DxListModule, DxCheckBoxModule } from 'devextreme-angular'; import { DxTreeViewModule, DxTreeViewComponent, DxTreeViewTypes } from 'devextreme-angular/ui/tree-view'; import { DxSelectBoxModule, DxSelectBoxTypes } from 'devextreme-angular/ui/select-box'; import { Service, Employee } from './app.service'; @Pipe({ name: 'title', standalone: true }) export class TitlePipe implements PipeTransform { transform(item: Record<string, unknown>): string { return item.text + (item.price ? ` ($${item.price})` : ''); } } if (!/localhost/.test(document.location.host)) { enableProdMode(); } @Component({ selector: 'demo-app', templateUrl: './app.component.html', styleUrls: ['./app.component.css'], providers: [Service], preserveWhitespaces: true, imports: [ DxTreeViewModule, DxListModule, DxCheckBoxModule, DxSelectBoxModule, TitlePipe, ], }) export class AppComponent implements AfterViewInit { @ViewChild(DxTreeViewComponent, { static: false }) treeView: DxTreeViewComponent<Employee>; employees: Employee[]; selectedEmployees: Employee[] = []; checkboxVisibilityOptions: DxTreeViewTypes.TreeViewCheckBoxMode[] = ['normal', 'selectAll', 'none']; checkboxVisibility = this.checkboxVisibilityOptions[0]; selectionModes: DxTreeViewTypes.SingleOrMultiple[] = ['multiple', 'single']; selectionMode = this.selectionModes[0]; disabledNodeSelectionModes: DxTreeViewTypes.DisabledNodeSelectionMode[] = ['never', 'recursiveAndAll']; disabledNodeSelectionMode = this.disabledNodeSelectionModes[0]; recursiveSelection = true; selectOnClick = false; isRecursiveDisabled = false; isSelectionModeDisabled = false; constructor(service: Service, private changeDetectorRef: ChangeDetectorRef) { this.employees = service.getEmployees(); } ngAfterViewInit() { this.syncSelection(this.treeView.instance); this.changeDetectorRef.detectChanges(); } treeViewSelectionChanged(e: DxTreeViewTypes.SelectionChangedEvent<Employee>) { this.syncSelection(e.component); } treeViewContentReady(e: DxTreeViewTypes.ContentReadyEvent<Employee>) { this.syncSelection(e.component); } syncSelection(treeView: DxTreeViewComponent<Employee>['instance']) { const selectedEmployees = treeView.getSelectedNodes() .map((node) => node.itemData); this.selectedEmployees = selectedEmployees; } checkboxVisibilityValueChanged(e: DxSelectBoxTypes.ValueChangedEvent) { this.checkboxVisibility = e.value; this.isSelectionModeDisabled = e.value === 'selectAll'; if (e.value === 'selectAll') { this.selectionMode = 'multiple'; this.isRecursiveDisabled = false; } } selectionModeValueChanged(e: DxSelectBoxTypes.ValueChangedEvent) { this.selectionMode = e.value; this.isRecursiveDisabled = e.value === 'single'; if (e.value === 'single') { this.recursiveSelection = false; this.treeView.instance.unselectAll(); } } disabledNodeSelectionModeValueChanged(e: DxSelectBoxTypes.ValueChangedEvent) { this.disabledNodeSelectionMode = e.value; } } bootstrapApplication(AppComponent, { providers: [ provideZoneChangeDetection({ eventCoalescing: true, runCoalescing: true }), ], });
::ng-deep .form > h4 { margin-bottom: 20px; } ::ng-deep .form > div, ::ng-deep #treeview { display: inline-block; vertical-align: top; } ::ng-deep .selected-container { padding: 20px; margin-left: 20px; background-color: rgba(191, 191, 191, 0.15); font-size: 115%; font-weight: bold; } ::ng-deep #selected-employees { margin-top: 20px; } ::ng-deep .selected-container .dx-list-item-content { padding-left: 0; } .options { padding: 20px; background-color: rgba(191, 191, 191, 0.15); margin-top: 20px; } .caption { font-size: 18px; font-weight: 500; } .option { width: 30%; margin-top: 10px; margin-right: 9px; box-sizing: border-box; display: flex; flex-direction: column; justify-content: center; } .options-container { display: flex; flex-direction: column; } .options-section { display: flex; } ::ng-deep .editor-container { height: 100%; display: flex; align-items: center; } ::ng-deep .editor-container > * { width: 100%; } ::ng-deep .option:last-of-type { margin-right: 0; }
import { Injectable } from '@angular/core'; export class Employee { id: number; fullName: string; prefix: string; position: string; expanded?: boolean; selected?: boolean; disabled?: boolean; items?: Employee[]; } const employees: Employee[] = [{ id: 1, fullName: 'John Heart', prefix: 'Dr.', position: 'CEO', expanded: true, items: [{ id: 2, fullName: 'Samantha Bright', prefix: 'Dr.', position: 'COO', expanded: true, disabled: true, items: [{ id: 3, fullName: 'Kevin Carter', prefix: 'Mr.', position: 'Shipping Manager', }, { id: 14, fullName: 'Victor Norris', prefix: 'Mr.', selected: true, position: 'Shipping Assistant', }], }, { id: 4, fullName: 'Brett Wade', prefix: 'Mr.', position: 'IT Manager', expanded: true, items: [{ id: 5, fullName: 'Amelia Harper', prefix: 'Mrs.', position: 'Network Admin', }, { id: 6, fullName: 'Wally Hobbs', prefix: 'Mr.', position: 'Programmer', }, { id: 7, fullName: 'Brad Jameson', prefix: 'Mr.', position: 'Programmer', }, { id: 8, fullName: 'Violet Bailey', prefix: 'Ms.', position: 'Jr Graphic Designer', }], }, { id: 9, fullName: 'Barb Banks', prefix: 'Mrs.', position: 'Support Manager', expanded: true, items: [{ id: 10, fullName: 'Kelly Rodriguez', prefix: 'Ms.', position: 'Support Assistant', }, { id: 11, fullName: 'James Anderson', prefix: 'Mr.', position: 'Support Assistant', }], }], }]; @Injectable() export class Service { getEmployees(): Employee[] { return employees; } }
<!DOCTYPE html> <html lang="en"> <head> <title>DevExtreme Demo</title> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=5.0" /> <link rel="stylesheet" type="text/css" href="https://cdn3.devexpress.com/jslib/26.1.5/css/dx.light.css" /> </head> <body class="dx-viewport"> <div class="demo-container"> <demo-app>Loading...</demo-app> </div> <script src="https://cdn.jsdelivr.net/npm/zone.js@0.16.2/bundles/zone.umd.js"></script> <script src="./bundle.js" type="module"></script> </body> </html>

Use the options pane below the TreeView to modify selection behavior as follows:

The TreeView allows you to update node selection states programmatically. You can select/deselect items by node keys, data objects, or DOM elements. To initially select nodes, configure selection fields within item objects (default: selected).

You can use data fields to customize items as your requirements dictate. This demo implements itemTemplate to merge text from multiple fields: you do not need to specify the text field for your TreeView data source.