I want to create a custom ListView that wrap the children with something common (and a few logic to disable them). However the children contents are unknown and different for each child and is managed by the parent.
Input (from, say, Index.razor
and the component is MyList.razor
):
<!-- Other code -->
<MyList>
<Child Title="Paragraph">
<p>May have any HTML content</p>
</Child>
<Child Title="Link">
<a href="://example.com">Example Link</a>
</Child>
<Child Title="Custom HTML">
<div>Could be anything in here</div>
<button onclick="this.OnButtonClicked">Click Me</button>
</Child>
</MyList>
The result:
<div >
<div >
<p>Row 1: Paragraph @*Title here*@</p>
<div > @*HTML content here*@
<p>May have any HTML content</p>
</div>
</div>
<div >
<p>Row 2: Link @*Title here*@</p>
<div > @*HTML content here*@
<a href="://example.com">Example Link</a>
</div>
</div>
<!-- More -->
</div>
I have tried templated components but the template is fixed for all chidden. Dynamically-rendered ASP.NET Core Razor components would require me to make a component class for each child which is not very desirable. Is there any solution to my question?
This is my best attempt so far but I do not know how to pass the RenderFragment
s to the children:
<app-board>
@{
var i = 0;
}
@foreach (var row in this.Rows)
{
var z = i;
<fieldset disabled="@(this.CurrentStep < z)">
<board-col>
<span >@(z)</span>
<span >@(row.Title)</span>
</board-col>
<board-col>
@(row.Html)
</board-col>
</fieldset>
}
</app-board>
public partial class AppBoard
{
[Parameter, AllowNull]
public IReadOnlyList<AppBoardRow> Rows { get; set; }
[Parameter]
public int CurrentStep { get; set; } = -1;
public record AppBoardRow(string Title, RenderFragment Html);
}
I don't know how I can use it in Index.razor
:
<AppBoard>
<!-- What's here? -->
</AppBoard>
CodePudding user response:
how to pass the RenderFragments to the children:
To insert the content provided by the parent component in a child component, a feature called Render Fragment is used. An example to explain its details.
First a parent component as follows:
@page "/ParentComponent"
<h1 >Parent Child Component</h1>
<ChildComponent Title="This title is passed as a parameter from the Parent Component">
A `Render Fragment` from the parent!
</ChildComponent>
<ChildComponent Title="This is the second child component"></ChildComponent>
@code {
}
Now, the child component as follows:
<div>
<div >@Title</div>
<div >
@if (ChildContent == null)
{
<span> Hello, from Empty Render Fragment </span>
}
else
{
<span>@ChildContent</span>
}
</div>
</div>
@code {
[Parameter]
public string Title { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
}
The Title public property, which is decorated by the Parameter property, enables the setting of a specific value by the component containing the ChildComponent.
Another public parameter is also defined here, this time it is of the special RenderFragment type. It can access the content that is set in the parent component of ChildComponent among its tags. Also, if this content is not set by the parent component, such as the second time the ChildComponent is placed on the page, a default content can be displayed by checking that it is null.
I think the problem with your code is that you have to define the parameter as the RenderFragment
.
CodePudding user response:
Render Fragment with a parameter RenderFragment<T>
. This provides a context of type T for the children.
Parent Component: (Add your own html etc). I use TItem
here to clarify its purpose.
@typeparam TItem
@foreach(var item in Data)
{
@ChildContent(item)
}
@code {
[Parameter, EditorRequired]
public RenderFragment<TItem> ChildContent { get; set; } = default!;
[Parameter, EditorRequired]
public IEnumerable<TItem> Data { get; set; } = default!;
}
This will iterate over the list and provide a context
of type TItem for ChildContent
.
Note: A renderfragment is a delegate. When you use <T>
this is saying the delegate has a parameter of type T
by default called context
Each child component needs to have its own
[Parameter, EditorRequired]
public Renderframent ChildContent { get; set;}
Usage
<ParentComponent Data=myList >
<ChildComponent1 Title="Paragraph">
<p>May have any HTML content</p>
</ChildComponent1>
</ParentComponent>
@code {
private List<ListItem> myList;
}
Anywhere inside ParentComponent
you can refer to the current item as context
. If your Item has a property of Name
for example you can use context.Name
.
You ChildComponent in this case are not aware of TItem however the context
will be available in the markup area of the composing component.
Clever things you can do from here:
Your child components can have the TItem passed in:
@typeparam TItem
@ChildContent(Item)
@code {
[Parameter, EditorRequired]
public RenderFragment<TItem> ChildContent { get; set; } = default!;
[Parameter, EditorRequired]
public TItem Item { get; set; } = default!;
}
usage
<ParentComponent Data=myList >
<ChildComponent1 Title="Paragraph" Item=context>
<p>May have any HTML content</p>
</ChildComponent1>
@context
</ParentComponent>
This presents a minor problem though you will have a nested context. To get around this you need to rename at least one to be unique.
<ParentComponent Data=myList Context="parentContext" >
<ChildComponent1 Title="Paragraph" Item=parentContext>
@context.Name //
@parentContext.Name // Both valid here
<p>May have any HTML content</p>
</ChildComponent1>
@parentContext.Name
</ParentComponent>