Dynamically render an unknown component instance with property values in Blazor (not child/parent)?

Viewed 563

Short question: is there anyway to render an already instantiated Component instance?

I want to have a dynamic content Dialog and the content should be made using a Component class, and they also may have custom properties, attributes etc.


I have the following Blazor structure where everything in MainComponent and Dialog are separated but can contact each other using IDialogService:

<main>
    <MainComponent />
</main>

<Dialog />

However, I want the content inside Dialog.razor to be dynamic, so I try this:

<dialog data-active="@(this.IsShowing)">
    <dialog-cover></dialog-cover>

    <dialog-container>
        @if (this.contentFragment != null)
        {
            @(this.contentFragment)
        }
    </dialog-container>
</dialog>

@code {

    RenderFragment contentFragment { get; set; }

    internal void SetFragments(ComponentBase dialog)
    {
        this.contentFragment = builder =>
        {
            builder.OpenComponent(0, dialog.GetType());
            builder.CloseComponent();
        };

        this.StateHasChanged();
    }

    // Other logic code for showing/hiding etc

}

This work as long as I pass a "plain" Component there, however, I want some dynamic text in, for example, ConfirmDialog.razor:

@if (this.Title != null)
{
    <header>
        <h2>@(this.Title)</h2>
    </header>
}

@if (this.Text != null)
{
    <main>
        @(this.Text)
    </main>
}

@code {

    public string Title { get; set; }
    public string Text { get; set; }

}

Usage:

    var diagConfirm = new ConfirmDialog()
    {
        Title = "Please confirm!",
        Text = "Something good happened!",
    };

    var result = await this.dialogService.ShowAsync(diagConfirm);

However, using the fragment, a new ConfirmDialog instance is created instead and diagConfirm is not used. Is there anyway to use the instance instead of just Component class name?

1 Answers

This is a temporary solution I am using for now. I know it's a hack and should not be a proper answer but it works for now.

        var properties = component.GetType().GetProperties();

        RenderFragment fragment = builder =>
        {
            builder.OpenComponent(0, component.GetType());

            foreach (var property in properties)
            {
                if (property.GetCustomAttribute<ParameterAttribute>() == null)
                {
                    continue;
                }

                var value = property.GetValue(component);

                builder.AddAttribute(0, property.Name, value);
            }

            builder.CloseComponent();
        };

Basically, I use Reflection to copy all the properties with Parameter to the newly created Component. This requires the Component properties to have [Parameter] attributes attached.

You will also get warnings from VS at these line, but you can safely ignore them if you know what you are doing.

    var diagConfirm = new LConfirmDialog()
    {
        Title = "Title",
        Text = "Hello",
    };

Component parameter 'Title' should not be set outside of its component

Related