How to pass a Blazor Component Instance to other Component and render it?

26 views Asked by At

I have a kind of factory in my application in which I create instances of various objects. All objects inherit from the same base class. I'm now trying to pass an instance of this derivation into a Component and would like to display the contents. I tried to implement this with a RenderFragment. This works for the initial view. However, the constructor of my derivation is called again by Blazor shortly afterwards and my instance in the html is a new one.

I need this to be able to react to interactions with my derivative. However, calling the constructor again leads to an instance which of course no longer contains any connections to my EventHandlers or the parameters that were originally given when the constructor was called.

ChildBase.cs

public class ChildBase : ComponentBase
{
   public EventHandler? OnClick = null;
}

Child1.razor

@inherits ChildBase

<div style="width:100px; height: 200px; background:red;>
   <button @onClick=OnClick>Click Child 1</button>
</div>

@code{

   private async Task OnClick()
   {
       // do something
       if(base.OnClick != null)
       {
           base.OnClick(this, EventArgs.Empty);
       }
   }
}

Child2.razor.cs

@inherits ChildBase

<div style="width:200px; height: 100px; background:yellow;>
   <button @onClick=OnClick>Click Child 2</button>
</div>

@code{

   private async Task OnClick()
   {
       // do something other
       if(base.OnClick != null)
       {
           base.OnClick(this, EventArgs.Empty);
       }
   }
}

Page1.razor

<button @onClick="ShowChild1">One</button>
<button @onClick="ShowChild2">Two</button>
<br/>
<br/>
@if(ContentFragment != null)
{
   @ContentFragment
}


@code{

private RenderFragment? ContentFragment;

private void ShowChild1()
{
   ChildBase child = new Child1();
   child.OnClick += OnChildClicked;
   ContentFragment = createRenderFragment(child);
}

private void ShowChild2()
{
   ChildBase child = new Child2();
   child.OnClick += OnChildClicked;
   ContentFragment = createRenderFragment(child);
}

private void OnChildClicked(object? sender, EventArgs e)
{
    //Do something
}

private RenderFragment createRenderFragment(ChildBase child) => builder =>
{
    builder.OpenComponent(0, child.GetType());
    builder.CloseComponent();
};
}

Child1 and Child2 each have a completely different view and completely different content.

When you click on the respective button, you will see a double call with a breakpoint in the constructor of the respective child and child base. The component displayed in the view no longer has any connection to the ClickHandler.

Is there a way to create an instance of a component and dynamically give it to a, I'll call it, parent component and display it in the parent component?

1

There are 1 answers

0
MrC aka Shaun Curtis On

You don't create Components, the Renderer does.

Here's a demo using your code showing how to build render fragments incorporating components:

ChildBase using an EventCallback rather than an event

public class ChildBase : ComponentBase
{
    [Parameter] public EventCallback OnClick { get; set; }
}

Child1 [and Child2]

@inherits ChildBase

<div style="m-2 p-2">
   <button class="btn btn-primary" @onclick="OnClicked">Click Child 1</button>
</div>

@code{

    private Task OnClicked()
    {
        return this.OnClick.InvokeAsync();
    }
}

There are several ways to put together the RenderFragments to tell the Renderer how to build the content. This shows one.

@page "/"
@using Microsoft.AspNetCore.Components.Rendering

<div class="m-2 p-2">
    <button class="btn btn-primary" @onclick="ShowChild1">One</button>
    <button class="btn btn-danger" @onclick="ShowChild2">Two</button>
</div>

@_contentFragment

@code {
    private RenderFragment? _contentFragment => BuildChild;
    private Type _componentToRender = typeof(Child1);

    private Task ShowChild1()
    {
        _componentToRender = typeof(Child1);
        return Task.CompletedTask;
    }

    private Task ShowChild2()
    {
        _componentToRender = typeof(Child2);
        return Task.CompletedTask;
    }

    private void BuildChild(RenderTreeBuilder builder)
    {
        builder.OpenComponent(0, _componentToRender);
        builder.AddComponentParameter(1, "OnClick", Microsoft.AspNetCore.Components.EventCallback.Factory.Create(this, this.OnChildClicked));
        builder.CloseComponent();
    }

    private Task OnChildClicked()
    {
        //Do something
        return Task.CompletedTask;
    }
}