Reputation: 18409
Can I create an instance of a Blazor component in C# and attach it afterwards? Alternatively can from C# code dynamically create a component in the DOM and get back a reference to it?
I'm creating a "popup dialog" triggered from the C# code.
Dialog.razor
<div>
Dialog Text: @Text
</div>
@code{
public string Text { get; set; }
}
Failed attempt
<div>
@foreach (var d in List)
{
<div>@d</div><!--this doesn't work since d is not a RenderFragment-->
}
</div>
@code{
public List<Dialog> List { get; set; } = new List<Dialog>();
void AddDialog()
{
var d = new Dialog();
d.Text = "Hello " + List.Count;
List.Add(d);
}
}
Problem here is that I haven't found a way to attach an instance of ComponentBase onto the DOM. Is there a way to get the RenderFragment?
Secondary problem is that child components aren't initialized until after the dialog is attached thus limiting what can be done in the first place.
Upvotes: 1
Views: 5604
Reputation: 45586
This is another way to do it...
<div style="border: 1px solid red; width: 500px; height:auto; margin: 3px;
padding:0px;">
<div style="height:auto; width:inherit; padding:5px; border: 1px solid
blue; text-align:right;">
<span style="float:left">@Title</span>
<a href="#" @onclick="@(() => Close.InvokeAsync(ID))"
role="button">X</a>
<div><input type="text" value="@content" /></div>
</div>
<div style="padding:25px; ">@ChildContent</div>
</div>
@code {
[Parameter]
public int ID { get; set; }
[Parameter]
public string Title { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
[Parameter]
public EventCallback <int> Close {get; set;}
}
@using Microsoft.AspNetCore.Components.CompilerServices;
<h3>AlertMessageGroup</h3>
@if (alerts.Count > 0)
{
<p>Contains @alerts.Count AlertMessage Components</p>
@foreach (var alert in alerts)
{
<p>Alert ID: @alert.ID</p>
}
}
<div>
@foreach (var alert in alerts)
{
@RenderAlert(alert);
}
</div>
@code {
List<Alert> alerts = new List<Alert>
{
new Alert{ ID = 1, Title = "First Message", Message = "This is my
first message" },
new Alert{ ID = 2, Title = "Second Message", Message = "This is
my second message" },
new Alert{ ID = 3, Title = "Third Message", Message = "This is my
third message" }
};
private RenderFragment RenderAlert(Alert alert) => builder =>
{
builder.OpenComponent(0, typeof(AlertMessage));
builder.AddAttribute(1, "ID", alert.ID);
builder.AddAttribute(2, "Title", alert.Title);
builder.AddAttribute(3, "ChildContent", (RenderFragment)((builder) =>
{
builder.AddContent(4, alert.Message);
}
));
builder.AddAttribute(5, "Close", EventCallback.Factory.Create<int>
(this, RemoveAlertMessage));
builder.CloseComponent();
};
public void RemoveAlertMessage(int ID)
{
alerts.Remove( alerts.Where(alert => alert.ID == ID).FirstOrDefault());
StateHasChanged();
}
public class Alert
{
public int ID { get; set; }
public string Title { get; set; }
public string Message { get; set; }
}
}
@page "/"
<AlertMessageGroup />
Upvotes: 5
Reputation: 18409
This is one way to do it
Placeholder
object and place it in a listOnAfterRender
before operating on the instance.Dialog.razor
Unaware of it being loaded dynamically.
<div>
Dialog Text: @Text
<Red @ref="Red"></Red>
</div>
@code{
public Red Red { get; set; }
public string Text { get; set; }
public void SetText(string text)
{
this.Text = text;
StateHasChanged();
}
}
DynamicWrapper.razor For notifying when the component is fully loaded
@ChildContent
@code{
[Parameter]
public RenderFragment ChildContent { get; set; }
[Parameter]
public EventCallback AfterFirstRender { get; set; }
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
await AfterFirstRender.InvokeAsync(null);
base.OnAfterRender(firstRender);
}
}
TestPage.razor
<div><button @onclick="AddDialog">Add one more</button></div>
<div>
@foreach (var p in List)
{
<DynamicWrapper AfterFirstRender="p.AfterFirstRender">
<Dialog @ref="p.Dialog"></Dialog>
</DynamicWrapper>
}
</div>
@code{
public class Placeholder<T>
{
public T Dialog { get; set; }
TaskCompletionSource<T> task = new TaskCompletionSource<T>();
public void AfterFirstRender(object args)
{
task.SetResult(Dialog);
}
public Task<T> GetDialog() => task.Task;
}
public List<Placeholder<Dialog>> List { get; set; } = new List<Placeholder<Dialog>>();
async Task AddDialog()
{
var p = new Placeholder<Dialog>();
List.Add(p);
var d = await p.GetDialog();
d.SetText("Hello");
d.Red.Show();
}
}
Upvotes: 2