How to apply hover background color to multi-line tree node in Angular Tree View

31 Aug 202614 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). When these heights are out of sync, users see inconsistent hover effects and selection areas that don’t cover the complete node content. Common causes are line wrap in the node text, custom nodeTemplate content, or theme-level line-height overrides.

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. If the application supports window resize, refire the height-sync routine inside a resize event handler so the rows stay aligned.

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:

  1. Event handlers that calculate and apply proper heights to hover elements
  2. 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/material3.css';
@import 'node_modules/@syncfusion/ej2-inputs/styles/material3.css';
@import 'node_modules/@syncfusion/ej2-buttons/styles/material3.css';
@import 'node_modules/@syncfusion/ej2-angular-base/styles/material3.css';
@import 'node_modules/@syncfusion/ej2-angular-navigations/styles/material3.css';
@import 'node_modules/@syncfusion/ej2-angular-inputs/styles/material3.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));