Reputation: 1352
I want to be able to take in a list of blazor components as a parameter in a declarative way.
Component 1:
@foreach(Component2 input in Inputs)
{
@input
}
@code{
[Parameter]
public List<Component2> Inputs {get; set;}
}
Component 2:
<h1>@Header</h1>
@code{
[Parameter]
public string Header {get; set;}
}
How I want to use it:
<Component1>
<Inputs>
<Component2 Header="hello" />
<Component2 Header="world" />
</Inputs>
</Component1>
Does anyone know if this is possible?
Thank you, Travis
Upvotes: 0
Views: 5659
Reputation: 30177
Here's a simplistic but more generic way to achieve what you want. It renders three instances of your HeaderComponent
and one Counter
.
@page "/"
<h3>MultiHeader</h3>
@MyComponents
@code {
private List<Tuple<Type, Dictionary<string, object>>> _components = new List<Tuple<Type, Dictionary<string, object>>>();
protected override void OnInitialized()
{
_components.Add(new Tuple<Type, Dictionary<string, object>>(typeof(HeaderComponent), new Dictionary<string, object>() { { "Header", "Hello 1" } }));
_components.Add(new Tuple<Type, Dictionary<string, object>>(typeof(HeaderComponent), new Dictionary<string, object>() { { "Header", "Hello 2" } }));
_components.Add(new Tuple<Type, Dictionary<string, object>>(typeof(HeaderComponent), new Dictionary<string, object>() { { "Header", "Hello 3" } }));
_components.Add(new Tuple<Type, Dictionary<string, object>>(typeof(Counter), new Dictionary<string, object>()));
}
private RenderFragment MyComponents => builder =>
{
foreach (var component in _components)
{
builder.OpenComponent(0, component.Item1);
foreach (var parameter in component.Item2)
{
builder.AddAttribute(1, parameter.Key, parameter.Value);
}
builder.CloseComponent();
}
};
}
I've used a Tuple
to keep things simple: you could have an object to hold your component information if you want to get a bit more complex.
Upvotes: 2
Reputation: 1352
While digging around the web I found a link to a Blazor University page that described this perfectly.
https://blazor-university.com/templating-components-with-renderfragements/creating-a-tabcontrol/
The gist of the solution is you send a reference of the parent to the child component. Then when the child is initialized you tell the parent to add the child to the list of components.
Parent Component:
<CascadingValue Value="this">
<div class="btn-group" role="group">
@foreach (Tab tabPage in Pages)
{
<button type="button"
class="btn @GetButtonClass(tabPage)"
@onclick=@( () => ActivatePage(tabPage) )>
@tabPage.Text
</button>
}
</div>
@ChildContent
</CascadingValue>
@code {
// Next line is needed so we are able to add <TabPage> components inside
[Parameter]
public RenderFragment ChildContent { get; set; }
public Tab ActivePage { get; set; }
List<Tab> Pages = new List<Tab>();
internal void AddPage(Tab tabPage)
{
Pages.Add(tabPage);
if (Pages.Count == 1)
ActivePage = tabPage;
StateHasChanged();
}
string GetButtonClass(Tab page)
{
return page == ActivePage ? "btn-primary" : "btn-secondary";
}
void ActivatePage(Tab page)
{
ActivePage = page;
}
}
Child Component:
@if (Parent.ActivePage == this)
{
@ChildContent
}
@code {
[CascadingParameter]
private Tabs Parent { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
[Parameter]
public string Text { get; set; }
protected override void OnInitialized()
{
if (Parent == null)
throw new ArgumentNullException(nameof(Parent), "TabPage must exist within a TabControl");
base.OnInitialized();
Parent.AddPage(this);
}
}
Usage:
<ParentComponent>
<ChildComponent Text="Tab 1">
<h1>This is a test</h1>
</ChildComponent >
<ChildComponent Text="2">
<h3>This is a test</h3>
</ChildComponent >
<ChildComponent Text="3 Tab">
<h5>This is a test</h5>
</ChildComponent >
</ParentComponent>
Upvotes: 2
Reputation: 37202
One option is to override BuildRenderTree. Here we define a MessagesComponent
that is supplied with a list of MessageComponents
, each of which has a single message:
// YourPage.Razor
<p>Here are some messages:</p>
<MessagesComponent MessageComponents="messageComponents" />
@code {
private readonly IEnumerable<MessageComponent> messageComponents = new List<MessageComponent>
{
new MessageComponent { Message = "Hello" },
new MessageComponent { Message = "World" }
};
}
// MessagesComponent.cs
using System.Collections.Generic;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
namespace WebApplication3.Pages
{
public class MessagesComponent : ComponentBase
{
[Parameter]
public IEnumerable<MessageComponent> MessageComponents { get; set; }
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
base.BuildRenderTree(builder);
// Render each component.
foreach (var component in MessageComponents)
{
builder.OpenComponent(0, typeof(MessageComponent));
builder.AddAttribute(1, nameof(MessageComponent.Message), component.Message);
builder.CloseComponent();
}
}
}
}
// MessageComponent.razor
@using Microsoft.AspNetCore.Components.Rendering
<p>This message says "@Message"</p>
@code {
[Parameter]
public string Message { get; set; }
}
Upvotes: 0