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

Grid Hierarchy

The Grid component provides options for visualizing the relations between parent and child records by displaying data in a hierarchical manner through a detail template.

In this article:

Basics

To implement hierarchy in the Grid, define a DetailTemplate under the main tag of the grid. In this template, you can access the model for the concrete row through the context, and use other components to show detailed data from it (for example, another grid, or any other set of components and HTML).

When a detail template is defined, an expand/collapse button is rendered at the beginning of the row that the user can click to show and hide the detailed data.

Define a detail template to show hierarchical data from the model in a nested grid

Click the + icon to expand the row details

<TelerikGrid Data="salesTeamMembers">
    <DetailTemplate>
        @{
            var employee = context as MainModel;
            <TelerikGrid Data="employee.Orders" Pageable="true" PageSize="5">
                <GridColumns>
                    <GridColumn Field="OrderId"></GridColumn>
                    <GridColumn Field="DealSize"></GridColumn>
                </GridColumns>
            </TelerikGrid>
        }
    </DetailTemplate>
    <GridColumns>
        <GridColumn Field="Id"></GridColumn>
        <GridColumn Field="Name"></GridColumn>
    </GridColumns>
</TelerikGrid>

@code {
    List<MainModel> salesTeamMembers { get; set; }

    protected override void OnInitialized()
    {
        salesTeamMembers = GenerateData();
    }

    private List<MainModel> GenerateData()
    {
        List<MainModel> data = new List<MainModel>();
        for (int i = 0; i < 5; i++)
        {
            MainModel mdl = new MainModel { Id = i, Name = $"Name {i}" };
            mdl.Orders = Enumerable.Range(1, 15).Select(x => new DetailsModel { OrderId = x, DealSize = x^i }).ToList();
            data.Add(mdl);
        }
        return data;
    }

    public class MainModel
    {
        public int Id { get; set; }
        public string Name { get;set; }
        public List<DetailsModel> Orders { get; set; }
    }

    public class DetailsModel
    {
        public int OrderId { get; set; }
        public double DealSize { get; set; }
    }
}

The result of the code snippet above, after expanding the second row

Blazor Hierarchy Grid In Grid

To have more levels, simply nest more grids and name the context variables. You can find an example in the Multi-Level Hierarchy KB article.

Expand Rows From Code

You can choose which detail templates will be expanded from your code through the grid state. Its ExpandedItems field contains a collection of the expanded Grid items (all detail templates are collapsed by default).

The ExpandedItems collection is compared against the Grid Data collection in order to determine which rows will be expanded. The default behavior of the framework is to compare objects by their reference.

When the ExpandedItems are obtained from a different data source to the Grid (e.g., from a separate service method and not from the view-model), the references may not match and so there will be no expanded items. In such cases, you have to override the Equals method of the underlying model class so that it matches them, for example, by a unique identifier rather than by reference so that two objects can be equal regardless of their origin, but according to their contents. When you are overriding the Equals method, it is also recommended to override the GetHashCode method as well. A similar example is available at Save and Load Grid State from Browser LocalStorage.

If you want to set an initial state to the Grid, use a similar snippet, but in the OnStateInit event

Expand DetailTemplate hierarchy from code

<TelerikGrid @ref="@GridRef"
             Data="@CategoryData"
             Pageable="true"
             PageSize="2">
    <GridToolBarTemplate>
        <TelerikDropDownList Data="@CategoryData"
                             @bind-Value="@DropDownListCategoryId"
                             TextField="@nameof(Category.Name)"
                             ValueField="@nameof(Category.Id)">
        </TelerikDropDownList>
        <TelerikButton ThemeColor="@ThemeConstants.Button.ThemeColor.Success"
                       OnClick="@ExpandCategory">Expand Category</TelerikButton>
        <span class="k-separator"></span>
        <TelerikButton ThemeColor="@ThemeConstants.Button.ThemeColor.Primary"
                       OnClick="@ExpandAll">Expand All Categories</TelerikButton>
        <span class="k-separator"></span>
        <TelerikButton OnClick="@CollapseAll">Collapse All Categories</TelerikButton>
    </GridToolBarTemplate>
    <GridColumns>
        <GridColumn Field="@(nameof(Category.Id))" Width="80px" />
        <GridColumn Field="@(nameof(Category.Name))" Title="Category Name" />
    </GridColumns>
    <DetailTemplate Context="category">
        <TelerikGrid Data="@ProductData.Where(x => x.CategoryId == category.Id)">
            <GridColumns>
                <GridColumn Field="@(nameof(Product.Name))" Title="Product Name" />
                <GridColumn Field="@(nameof(Product.Price))" DisplayFormat="{0:c2}" />
                <GridColumn Field="@(nameof(Product.Quantity))" />
            </GridColumns>
        </TelerikGrid>
    </DetailTemplate>
</TelerikGrid>

@code {
    private TelerikGrid<Category>? GridRef { get; set; }

    private List<Category> CategoryData { get; set; } = new();

    private List<Product> ProductData { get; set; } = new();

    private int DropDownListCategoryId { get; set; } = 1;

    private async Task ExpandCategory()
    {
        if (GridRef != null)
        {
            var gridState = GridRef.GetState();

            var categoryToExpand = CategoryData.First(x => x.Id == DropDownListCategoryId);

            gridState.ExpandedItems.Add(categoryToExpand);

            await GridRef.SetStateAsync(gridState);
        }
    }

    private async Task ExpandAll()
    {
        if (GridRef != null)
        {
            var gridState = GridRef.GetState();

            gridState.ExpandedItems = CategoryData;

            await GridRef.SetStateAsync(gridState);
        }
    }

    private async Task CollapseAll()
    {
        if (GridRef != null)
        {
            var gridState = GridRef.GetState();

            gridState.ExpandedItems.Clear();

            await GridRef.SetStateAsync(gridState);
        }
    }

    protected override void OnInitialized()
    {
        var categoryCount = 3;

        for (int i = 1; i <= categoryCount; i++)
        {
            CategoryData.Add(new Category()
            {
                Id = i,
                Name = $"Category {i}"
            });
        }

        for (int i = 1; i <= 12; i++)
        {
            ProductData.Add(new Product()
            {
                Id = i,
                CategoryId = i % categoryCount + 1,
                Name = $"Product {i}",
                Price = Random.Shared.Next(1, 100) * 1.23m,
                Quantity = Random.Shared.Next(0, 100)
            });
        }
    }

    public class Category
    {
        public int Id { get; set; }
        public string Name { get; set; } = string.Empty;
    }

    public class Product
    {
        public int Id { get; set; }
        public int CategoryId { get; set; }
        public string Name { get; set; } = string.Empty;
        public decimal Price { get; set; }
        public int Quantity { get; set; }
    }
}

More Examples

The following articles and sample projects can be helpful when implementing hierarchy:

See Also

In this article