Introduction
In this article, we will delve into designing a Tree component that provides a nested view of data, allowing users to effortlessly expand and collapse different levels to reveal or hide details. This functionality is especially valuable when managing data with parent-child relationships. It is the best form to represent hierarchical data.
By the end of the article, you'll be able to implement an interactive Tree component as shown below for your applications.
Please note that this is a two-part series, and my upcoming article focuses on the seamless integration of the Tree and DataGrid components. Next chapter: "Tree and DataGrid Components: Exploring the Perfect Blend"

Tree component in Blazor
We'll be building a Tree component which needs 4 essential elements to bring it to life.
- PhoneService: This class encapsulates the dummy data and implements an associated interface, "IPhoneService".
- Tree. razor: The core of our Tree component; this is where we define the structure of the tree.
- Tree. razor.cs: The C# code-behind file of the Tree component responsible for the behavior and data manipulation of the component.
- Tree.razor.css: This CSS file to make Tree, this houses styles for toggle buttons, nodes, and nested levels.
Step 1. Phone service
To begin, let's establish a dedicated space for managing data-related constructs. Create a directory named "Data" within your project. We need a contract interface, a model class, and a service class. Let's begin with an interface.
Defining the Interface "IPhoneService"
Our first step involves creating an interface that outlines the contract for data retrieval. Within the "Data" folder, define "IPhoneService" as follows.
public interface IPhoneService
{
List<Phone> GetPhones();
}
Code Snippet 1: interface IPhoneService
Crafting the Model
Now, let's create a model that represents the data. Create a class "Phone" under the "Data" folder as follows.
public class Phone
{
public string Manufacturer { get; set; }
public string Name { get; set; }
public decimal Cost { get; set; }
public string Type { get; set; }
public string OS { get; set; }
}
Code Snippet 2: class Phone
Adding a dummy Data
Now, let's populate our service with some dummy data. Implement "GetPhones()" method adhering to the "IPhoneService" interface.
public class PhoneService : IPhoneService
{
public List<Phone> GetPhones()
{
return new List<Phone>
{
new Phone { Manufacturer = "Apple", Name = "iPhone 14", Cost = 699, Type = "Smartphone", OS = "iOS 15" },
new Phone { Manufacturer = "Apple", Name = "iPhone 14 Pro", Cost = 999, Type = "Smartphone", OS = "iOS 15" },
new Phone { Manufacturer = "Apple", Name = "iPhone 14 Pro Max", Cost = 1599, Type = "Smartphone", OS = "iOS 16" },
new Phone { Manufacturer = "Samsung", Name = "Galaxy Z Fold", Cost = 1999, Type = "Smartphone", OS = "Android 11" },
new Phone { Manufacturer = "Samsung", Name = "Galazy S23", Cost = 1499, Type = "Smartphone", OS = "Android 12" },
new Phone { Manufacturer = "Google", Name = "Pixel 7", Cost = 699, Type = "Smartphone", OS = "Android 11" },
new Phone { Manufacturer = "Google", Name = "Pixel 7 Pro", Cost = 849, Type = "Smartphone", OS = "Android 12" }
};
}
}
Code Snippet 3: class PhoneService
Registering a service
Go to "program.cs" and add the following line from code snippet 4. This ensures that our "PhoneService" is readily available throughout your app.
builder.Services.AddSingleton<IPhoneService, PhoneService>();
Code Snippet 4: Adding singleton in the program.cs
Step 2. Tree.razor component
In the following code snippet 5, we have added a new component, "Tree.razor" under "Pages" folder. Here, we are looping through manufacturers, then OS, and finally through phones. we have toggles button [+] and [-] which expands and collapse the level below. The first node is the manufacturer. Upon clicking the [+] button, it will expand lower level where the list is divided by the version of OS. and at the leaf node, we have name of the phones.
@page "/tree"
@using System.Collections.Generic
@using TreeAndDataGridComponents.Data
@inject IPhoneService phoneService;
@inject IJSRuntime jsRuntime;
<h3>Tree Component</h3>
<!-- Tree.razor -->
<ul class="tree">
<!-- Loop through manufacturers -->
@foreach (var manufacturer in GetDistinctManufacturers())
{
<li>
<!-- Top level: Manufacturer -->
<span class="toggle"
@onclick="() => ToggleManufacturer(manufacturer)">
@(IsManufacturerExpanded(manufacturer) ? "[-]" : "[+]")
</span>
<span>@manufacturer</span>
<ul class="nested"
style="display: @(IsManufacturerExpanded(manufacturer) ? "block" : "none")">
<!-- Loop through OS -->
@foreach (var os in GetDistinctOS(manufacturer))
{
<li>
<!-- Nested level: OS -->
<span class="toggle"
@onclick="() => ToggleOS(manufacturer, os)">
@(IsOSExpanded($"{manufacturer}/{os}") ? "[-]" : "[+]")
</span>
<span>@os</span>
<ul class="nested"
style="display: @(IsOSExpanded($"{manufacturer}/{os}") ? "block" : "none")">
<!-- Loop through phones -->
@foreach (var phone in GetPhonesByManufacturerAndOS(manufacturer, os))
{
<!-- Last level: Phone -->
<li>@phone.Name</li>
}
</ul>
</li>
}
</ul>
</li>
}
</ul>


Join the conversation! Your thoughts help the community grow.