Creating a Repeater component with Blazor

 
 
  • Gérald Barré

If you are familiar with the old ASP.NET, you know the <asp:Repeater> control. This control iterates on a collection (may come from any kind of DataSource) and use a template to generate the page.

ASP.NET
<%-- This is ASP.NET code (not ASP.NET Core) --%>
<asp:Repeater runat="server" DataSourceID="ObjectDataSource1">
    <HeaderTemplate>
        <table>
            <tr>
    </HeaderTemplate>
    <ItemTemplate>
                <td><%# Eval("CategoryName") %></td>
    </ItemTemplate>
    <FooterTemplate>
            </tr>
        </table>
    </FooterTemplate>
</asp:Repeater>

Of course, nobody wants to write old ASP.NET code now. Instead, you may want to use modern technologies such as ASP.NET Core and Blazor. In the default Blazor application template, it uses a foreach loop to iterates on the collection and display data. Also, it needs to take care of the case where the data is still loading and displays a loading message instead of the table. Here's the code of the FetchData page:

Razor
@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>Date</th>
                <th>Temp. (C)</th>
                <th>Summary</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var forecast in forecasts)
            {
                <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.Summary</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    private WeatherForecast[] forecasts;

    protected override async Task OnInitializedAsync()
    {
        forecasts = await ForecastService.GetForecastAsync(DateTime.UtcNow);
    }
}

This code is easy to read, but you may need to write it many times in your application. Of course, you are aware that more code means more bugs! Instead of duplicating code everywhere, you can create a component to encapsulate the layout logic.

#Creating the Repeater component

The component usage looks like the following:

Razor
<Repeater Items="@items">

    <LoadingTemplate>
        <p><em>Loading...</em></p>
    </LoadingTemplate>

    <RepeaterContainerTemplate Context="ItemsContent">
        <table class="table">
            <thead>
                <tr>
                    <th>Date</th>
                    <th>Temp. (C)</th>
                    <th>Summary</th>
                </tr>
            </thead>
            <tbody>
                @ItemsContent
            </tbody>
        </table>
    </RepeaterContainerTemplate>

    @* Context allows to rename the default @context variable to a more meaningful name. In this cas @forecase *@
    <ItemTemplate Context="forecast">
        <tr>
            <td>@forecast.Date.ToShortDateString()</td>
            <td>@forecast.TemperatureC</td>
            <td>@forecast.Summary</td>
        </tr>
    </ItemTemplate>

</Repeater>

You can see that it uses templates, so it is reusable and more readable. There is no code, only declarations that reduce the possibilities of a bug in the code.

The Repeater component is very simple. It's composed of only one file named Repeater.razor with the following content:

Razor
@* T is the type of objects to enumerate in the repeater *@
@* It should be inferred automatically based on the Items property *@
@* You can specify it manually by using <Repeater T=Person /> *@
@typeparam T

@if (Items == null)
{
    @LoadingTemplate
}
else
{
    if (EmptyTemplate != null && !Items.Any())
    {
        @EmptyTemplate
    }
    else
    {
        @* "@:" allows to switch from C# to Razor. In this case this creates block that will evaluate as a RenderFragment *@
        @RepeaterContainerTemplate(
            @: @{
                var first = true;
                foreach (var item in Items)
                {
                    if(!first && ItemSeparatorTemplate != null)
                    {
                        @ItemSeparatorTemplate
                    }

                    @ItemTemplate(item);
                    first = false;
                }
            }
            )
    }
}

@code {
    [Parameter]
    public IEnumerable<T> Items { get; set; }

    [Parameter]
    public RenderFragment LoadingTemplate { get; set; }

    [Parameter]
    public RenderFragment<RenderFragment> RepeaterContainerTemplate { get; set; }

    [Parameter]
    public RenderFragment<T> ItemTemplate { get; set; }

    [Parameter]
    public RenderFragment ItemSeparatorTemplate { get; set; }

    [Parameter]
    public RenderFragment EmptyTemplate { get; set; }

    protected override void OnParametersSet()
    {
        // Create empty template in case the user doesn't provide it
        if (RepeaterContainerTemplate == null)
        {
            RepeaterContainerTemplate = new RenderFragment<RenderFragment>(fragment => fragment);
        }
    }
}

The code is pretty simple. You could add many features to the repeater such as an AlternateItemTemplate.

This component is very basic, but you can create similar components for different layout (WrapPanel, Grid, etc.). This way you can extract complex code into reusable components! Components are very useful, don't hesitate to create a few of them when needed.

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?Buy Me A Coffee💖 Sponsor on GitHub