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

Dynamic Templates that can be repeated easily

Environment

Product Grid for Blazor,
TreeList for Blazor,
UI for Blazor

Description

When working with templated columns some code is repetitive for each Column that needs to be in a template (we have a lot of templates in each grid) and I want an easier and more reusable way to write templates.

How can I define my template code in C# so it is easier to reuse?

I have dynamic objects with many fields and I want to pass the object and field name to the template so I can extract the information I need.

Solution

While all of this is possible with templates in the markup, some code may get repetitive (especially casting the context) and/or may be harder to debug. You can extract templates to the C# portion of your component by defining the RenderFragment object yourself.

Another option is, of course, to create a child component that receives the context and provides the required casting and rendering on its own. This is a well documented approach in the Blazor framework so this article will focus on the specific way of writing a RenderFragment.

Using a RenderFragment in the C# code to create a template by passing a field name to it so you can extract particular information based on that field with Reflection.

@using System.Reflection;

<TelerikGrid Data="@forecasts" Height="550px" Pageable="true">
    <GridColumns>
        <GridColumn Template="@(GetColumnTemplate("Id"))"
            Field="Id" Title="Id" Width="100px" Editable="false" Groupable="false">
        </GridColumn>
        <GridColumn Template="@(GetColumnTemplate("Date"))"
            Field="Date" Width="220px">
        </GridColumn>
        <GridColumn Field="Summary">
        </GridColumn>
    </GridColumns>
</TelerikGrid>

@code {
    List<WeatherForecast> forecasts { get; set; }

    protected override void OnInitialized()
    {
        GetForecasts();
    }

    void GetForecasts()
    {
        forecasts = Enumerable.Range(1, 20).Select(x => new WeatherForecast()
        {
            Id = x,
            Date = DateTime.Now.AddDays(x),
            Summary = "Summary" + x
        }).ToList();
    }

    public class WeatherForecast
    {
        public int Id { get; set; }
        public DateTime Date { get; set; }
        public string Summary { get; set; }
    }

    public RenderFragment<object> GetColumnTemplate(string propName)
    {
        // Define the RenderFragment in your code
        // Its type matches the type of the Grid context - an object
        // The same as if you were defining it in the markup

        // The syntax for writing a RenderFragment is rather specific, note the lambda expressions

        RenderFragment<object> ColumnTemplate = context => __builder =>
        {
            // in this example we pass the property name from the grid declaration
            // and we use reflection to extract the needed data. You don't have to
            // If you know the field or the type, you can cast and simplify this code as needed

            PropertyInfo propertyInfo = context.GetType().GetProperty(propName);

            var propType = propertyInfo.PropertyType;

            var propValue = propertyInfo.GetValue(context);

            if (propType == typeof(int))
            {
                <div style="text-align: right;">
                    @propValue
                </div>
            }
            else
            {
                @propValue
            }
        };

        return ColumnTemplate;
    }
}

Notes

You can combine this approach with a loop over a column descriptor so you can create many columns based on what you seek. Of course, you can declare the template in the markup too.

Generally, casting of the Template context is required because an object is passed to the RenderFragment. Currently, there is no option to pass the typeparam of a parent component down to its children, and that is why we are using an object instead of the TItem that you have bound the Grid to: https://github.com/dotnet/aspnetcore/issues/7268

For example, the RowTemplate of the Grid uses RenderFragment<TItem> where no additional casting is needed, but the cell template is a child component of the row so it can only get an object.

To summarize, once the framework provides the ability to pass the typeparam to child components, there will be no need to cast the context to the model and the code will look cleaner. Until then, there is no other way to avoid those lines of casting.

In this article