New to Telerik UI for Blazor? Download free 30-day trial

Treeview Data Binding to Hierarchical Data

This article explains how to bind the TreeView for Blazor to hierarchical data. Before continuing, make sure you are familiar with the treeview data binding basics.

Hierarchical data means that the child items are provided in a property of the parent item. By default, the TreeView expects this property to be called Items, otherwise set the property name in the ItemsField parameter. If a certain node has children, it will render an expand icon. The HasChildren model property can override this, but it is not required for hierarchical data binding.

The hierarchical data binding approach allows you have separate collections of data or different model types at each TreeView level. Note that the data binding settings are per level, so a certain level will always use the same bindings, regardless of the model they represent and their parent.

Consider the following examples:

Different Types at Each Level

The example below uses two levels of hierarchy, but the same idea applies to any number of levels. You will likely need a separate TreeViewBinding tag for each level with its own field name configuration.

TreeView with different model type at each all level

Hierarchical data hold collections of the child items

<TelerikTreeView Data="@HierarchicalData" @bind-ExpandedItems="@ExpandedItems">
    <TreeViewBindings>
        <TreeViewBinding TextField="Category" ItemsField="Products" />
        <TreeViewBinding Level="1" TextField="ProductName" />
    </TreeViewBindings>
</TelerikTreeView>

@code {
    public IEnumerable<ProductCategoryItem> HierarchicalData { get; set; }
    public IEnumerable<object> ExpandedItems { get; set; } = new List<object>();

    public class ProductCategoryItem
    {
        public string Category { get; set; }
        public List<ProductItem> Products { get; set; }
    }

    public class ProductItem
    {
        public string ProductName { get; set; }
    }


    protected override void OnInitialized()
    {
        LoadHierarchical();
        ExpandedItems = HierarchicalData.Where(x => x.Products != null && x.Products.Any()).ToList();
    }

    private void LoadHierarchical()
    {
        List<ProductCategoryItem> roots = new List<ProductCategoryItem>();

        List<ProductItem> firstCategoryProducts = new List<ProductItem>()
    {
            new ProductItem { ProductName= "Category 1 - Product 1" },
            new ProductItem { ProductName= "Category 1 - Product 2" }
        };

        roots.Add(new ProductCategoryItem
        {
            Category = "Category 1",
            Products = firstCategoryProducts // this is how child items are provided

        });

        roots.Add(new ProductCategoryItem
        {
            Category = "Category 2" // we will set no other properties and it will not have children, nor will it be expanded
        });

        HierarchicalData = roots;
    }
}

Same Model Type on All Levels

The example below uses the default property names in the model (Id, Text, Items), so there is no need to set field parameters in the TreeView configuration.

Experiment with the TreeLevels, RootItems and ItemsPerLevel values below.

TreeView with random number of levels and same model type on all levels

<TelerikTreeView Data="@HierarchicalData"
                 CheckBoxMode="@TreeViewCheckBoxMode.Multiple"
                 CheckChildren="true"
                 CheckParents="true"
                 SelectionMode="@TreeViewSelectionMode.Multiple"
                 @bind-ExpandedItems="@ExpandedItems"
                 @bind-CheckedItems="@CheckedItems"
                 @bind-SelectedItems="@SelectedItems" />

@code {
    private List<TreeItem> HierarchicalData { get; set; } = new();
    private IEnumerable<object> ExpandedItems { get; set; } = new List<TreeItem>();
    private IEnumerable<object> CheckedItems { get; set; } = new List<TreeItem>();
    private IEnumerable<object> SelectedItems { get; set; } = new List<TreeItem>();

    private int TreeLevels { get; set; } = 4;
    private int RootItems { get; set; } = 3;
    private int ItemsPerLevel { get; set; } = 2;
    private int IdCounter { get; set; }

    protected override void OnInitialized()
    {
        HierarchicalData = LoadHierarchical();

        // Select, check and expand the root items.
        ExpandedItems = new List<TreeItem>(HierarchicalData);
        // CheckChildren and CheckParents don't affect programmatic checking.
        CheckedItems = new List<TreeItem>(HierarchicalData);
        SelectedItems = new List<TreeItem>(HierarchicalData);
    }

    private List<TreeItem> LoadHierarchical()
    {
        List<TreeItem> items = new List<TreeItem>();

        PopulateItems(items, 1);

        return items;
    }

    private void PopulateItems(List<TreeItem> items, int level)
    {
        for (int i = 1; i <= (level == 1 ? RootItems : ItemsPerLevel); i++)
        {
            var itemId = ++IdCounter;

            var newItem = new TreeItem()
            {
                Id = itemId,
                Text = $"Level {level} Item {i} Id {itemId}"
            };

            items.Add(newItem);

            if (level < TreeLevels)
            {
                PopulateChildren(items, level + 1);
            }
        }
    }

    private void PopulateChildren(List<TreeItem> items, int level)
    {
        foreach (var item in items)
        {
            item.Items = new List<TreeItem>();

            PopulateItems(item.Items, level);
        }
    }

    public class TreeItem
    {
        public int Id { get; set; }
        public string Text { get; set; } = string.Empty;
        public List<TreeItem>? Items { get; set; }
    }
}

See Also

In this article