How do you render a list of components in a loop (Blazor)?

9.5k Views Asked by At

I must be missing something very obvious with blazor... I want to simply render a list containing a component, yet there's no (obvious?) way to reference the iterator (which is a component) for rendering?

TodoList.razor

<input @bind="_newTodo" />
<button @onclick="@AddTodoItem">+</button>

@foreach (TodoItem todoItem in _todoItems)
{
    // todoItem is a razor component, yet I can't simply render it here?
    // <todoItem />
}

@code {
    private IList<TodoItem> _todoItems = new List<TodoItem>();
    private string _newTodo;

    private void AddTodoItem()
    {
        if (!string.IsNullOrWhiteSpace(_newTodo))
        {
            _todoItems.Add(new TodoItem { Title = _newTodo });
            _newTodo = string.Empty;
        }
    }
}

TodoItem.razor

<span>@Title</span>

@code {
    public string Title { get; set; }
}
5

There are 5 best solutions below

3
Vencovsky On BEST ANSWER

One solution to do that is have a class that holds the component properties and pass the properties to it

<input @bind="_newTodo" />
<button @onclick="@AddTodoItem">+</button>

@foreach (TodoItem todoItem in _todoItemsDto)
{
    // Pass the Dto properties to the component
    <TodoItem Title="@todoItem.Title" />
}

@code {
    private IList<TodoItemDto> _todoItemsDto = new List<TodoItemDto>();
    private string _newTodo;

    class TodoItemDto {
        public string Title { get; set; }
    }

    private void AddTodoItem()
    {
        if (!string.IsNullOrWhiteSpace(_newTodo))
        {
            _todoItems.Add(new TodoItemDto { Title = _newTodo });
            _newTodo = string.Empty;
        }
    }
}
0
AudioBubble On

I just built a Help system that has a LinkButton component, and I render it like this:

 foreach (HelpCategory category in Categories)
 {
     <LinkButton Category=category Parent=this></LinkButton>
     <br />
 }

Each HelpCategory has one or more Help Articles that can be expanded.

Here is the code for my LinkButton, it does more of the same:

@using DataJuggler.UltimateHelper.Core
@using ObjectLibrary.BusinessObjects

@if (HasCategory)
{
    <button class="linkbutton" 
    @onclick="SelectCategory">@Category.Name</button>

    @if (Selected)
    {
        <div class="categorydetail">
            @Category.Description
        </div>
        <br />
        <div class="margintop">
            @if (ListHelper.HasOneOrMoreItems(Category.HelpArticles))
            {
                foreach (HelpArticle article in Category.HelpArticles)
                {
                    <ArticleViewer HelpArticle=article Parent=this> 
                    </ArticleViewer>
                    <br />
                    <div class="smallline"></div>
                }
            }
        </div>
    }
}
0
Ashijo On

This may not be the best way to do it but it will avoid having 50+ attributes to set in the tag.

Component :


  <h1>@Title</h1> 
  <h2>@Description</h2>


@code {
    public string? Title { get; set; }
    public string? Description { get; set; }
    
    [Parameter]
    public KanbanTask? Origin //KanbanTask is how I named this same component
    {
        get { return null; }
        set
        {
            Title = value?.Title;
            Description = value?.Description;
        }
    }
}

Then how to call it :

   @foreach (var todoTask in TodoList)
            {
                <KanbanTask Origin="@todoTask" />
            }

This is using the set of a property has a constructor. It works, but I think it's not excellent since set was not made for it in the first instance. If anyone else has an idea to make it better I'm interested

1
Ibrahim Timimi On

Sometimes the obvious solutiton is simpler and better.

TodoItem:

<span>@Title</span>

@code {
    [Parameter] // add this parameter to accept title 
    public string Title { get; set; }
}

Page:

<input @bind="_newTodo"/>
<button @onclick="@AddTodoItem">+</button>

<ol>
    @foreach (var todoItem in _todoItems)
    {
        <li>
            <TodoItem Title="@todoItem.Title"/>
        </li>
    }
</ol>

@code {
    private readonly IList<TodoItem> _todoItems = new List<TodoItem>();
    private string _newTodo;

    private void AddTodoItem()
    {
        if (!string.IsNullOrWhiteSpace(_newTodo))
        {
            _todoItems.Add(new TodoItem { Title = _newTodo });
            _newTodo = string.Empty;
        }
    }
}

Output:
enter image description here

0
Greg Gum On

Yes, of course you can render a list with foreach. This article covers it well.

Here is an example. Note the use of the item in the click event so you know which item was clicked on. Note that this must be done using a lambda.

<section data-info="List of images">
@foreach (var item in this.Parent.CurrentCard.Images.OrderByDescending(a => a.InsertedDate))
{
    <div class="border border-secondary m-2">
        <img class="img-fluid" src="/api/image/fetch/@item.StorageName" alt="@item. Filename">

        <div class="card-body">
            <h5 class="card-title">Card title</h5>
            <p class="card-text">Some quick example text to build on the card title and make up the bulk of the card's content.</p>
            <a href="#" @onclick="()=> RemoveImage(item)" class="btn btn-secondary">Remove</a>
        </div>
    </div>
}