Hover multi line tree node in Angular TreeView component
27 Aug 202513 minutes to read
When working with TreeView nodes that contain multi-line content, the default hover and selection behaviors may not align properly with the actual content height. This guide demonstrates how to create consistent hover and selection effects that cover the entire multi-line node content area.
Overview
Multi-line TreeView nodes present a unique challenge where the hover area (e-fullrow
element) needs to match the actual content height (e-text-content
element). Without proper height synchronization, users may experience inconsistent hover effects or selection areas that don’t cover the complete node content.
This implementation uses the TreeView component’s created
and nodeSelecting
events to dynamically adjust row heights and ensure proper hover behavior across all multi-line nodes.
Implementation
The solution involves two key components:
- Event handlers that calculate and apply proper heights to hover elements
- CSS styling that accommodates multi-line content layout
Component Setup
Configure the TreeView component with the necessary event handlers and styling:
import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { FormsModule } from '@angular/forms'
import { TreeViewModule } from '@syncfusion/ej2-angular-navigations'
import { Component, Inject, ViewChild } from '@angular/core';
import { TreeViewComponent } from '@syncfusion/ej2-angular-navigations';
import { NodeSelectEventArgs } from '@syncfusion/ej2-navigations';
/**
* Hovering multiple line treeview
*/
@Component({
imports: [
FormsModule, TreeViewModule
],
standalone: true,
selector: 'app-container',
template: `<div id='treeparent'><ejs-treeview id='treeElement' #treevalidate [fields]='field' (nodeSelecting)='onSelect($event)' cssClass="customTree" (created)="onCreate($event)"></ejs-treeview></div>`
})
export class AppComponent {
// Data source for TreeView component
public hierarchicalData: Object[] = [
{
id: 1, name: 'Web Control sWeb ControlsWeb ControlsWeb ControlsWeb ControlsWeb ControlsWeb ControlsWeb Controls', expanded: true,
child: [
{
id: 2, name: 'CalendarCalendarCalendarCalendarCalendarCalendarCalendarCalendarCalendarCalendarCalendarCalendarCalendar', child: [
{ id: 7, name: 'Constructors' },
{ id: 8, name: 'Properties' },
{ id: 9, name: 'Methods' },
{ id: 10, name: 'Events' }
]
},
{
id: 3, name: 'Data Grid', child: [
{ id: 11, name: 'Constructors' },
{ id: 12, name: 'Fields' },
{ id: 13, name: 'Properties' },
{ id: 14, name: 'Methods' },
{ id: 15, name: 'Events' }
]
},
{
id: 4, name: 'DropDownList', child: [
{ id: 16, name: 'Constructors' },
{ id: 17, name: 'Properties' },
{ id: 18, name: 'Methods' }
]
},
{
id: 5, name: 'Menu', child: [
{ id: 19, name: 'Constructors' },
{ id: 20, name: 'Fields' },
{ id: 21, name: 'Properties' },
{ id: 22, name: 'Methods' },
{ id: 23, name: 'Events' }
]
}
]
},
{
id: 24, name: 'Web Controls',
child: [
{
id: 25, name: 'Calendar', child: [
{ id: 26, name: 'Constructors' },
{ id: 27, name: 'Properties' },
{ id: 28, name: 'Methods' },
{ id: 29, name: 'Events' }
]
},
{
id: 30, name: 'Data Grid', child: [
{ id: 31, name: 'Constructors' },
{ id: 32, name: 'Fields' },
{ id: 33, name: 'Properties' },
{ id: 34, name: 'Methods' },
{ id: 35, name: 'Events' }
]
}
]
}
];
public field: Object = { dataSource: this.hierarchicalData, id: 'id', text: 'name', child: 'child' };
@ViewChild('treevalidate') tree?: TreeViewComponent;
// Triggers on node selection
public onSelect(args: NodeSelectEventArgs): void {
this.setHeight(args.node);
}
public onCreate(args: any) {
// Triggers on mouse hover/keydown event
['mouseover', 'keydown'].forEach(evt =>
this.tree?.element.addEventListener(evt, (event) => { this.setHeight(event.target); }));
}
// Sets e-fullrow to be the same as e-text-content
public setHeight(element: any) {
if (this.tree?.fullRowSelect) {
if (element?.classList.contains("e-treeview")) {
element = element.querySelector(".e-node-focus").querySelector(".e-fullrow");
}
else if (element.classList.contains("e-list-parent")) {
element = element.querySelector(".e-fullrow");
}
else if (element.classList.value != ("e-fullrow") && element.closest(".e-list-item")) {
element = element.closest(".e-list-item").querySelector(".e-fullrow");
}
if (element.nextElementSibling)
element.style.height = element.nextElementSibling.offsetHeight + "px";
}
}
}
@import 'node_modules/@syncfusion/ej2-base/styles/material.css';
@import 'node_modules/@syncfusion/ej2-inputs/styles/material.css';
@import 'node_modules/@syncfusion/ej2-buttons/styles/material.css';
@import 'node_modules/@syncfusion/ej2-angular-base/styles/material.css';
@import 'node_modules/@syncfusion/ej2-angular-navigations/styles/material.css';
@import 'node_modules/@syncfusion/ej2-angular-inputs/styles/material.css';
#treeparent {
display: block;
max-width: 400px;
max-height: 320px;
margin: auto;
overflow: auto;
border: 1px solid #dddddd;
border-radius: 3px;
}
.customTree li .e-list-text{
white-space: normal;
word-break: break-all;
}
import { bootstrapApplication } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import 'zone.js';
bootstrapApplication(AppComponent).catch((err) => console.error(err));