0
votes

Say we have a basic component ParentComponent.razor:

<div id="ParentComponent">@ChildContent</div>

@code {
    [Parameter] public RenderFragment ChildContent {get;set;}
}

This can be used like this:

<ParentComponent>
    <div>content</div>
</ParentComponent>

Which would render:

<div id="ParentComponent"><div>content</div></div>

But say we want a ChildComponent.razor to inherit from ParentComponent.razor:

@inherits ParentComponent

<div id="ChildComponent">@ChildContent</div>

@code {
    [Parameter] public RenderFragment ChildContent {get;set;}
}

Is there any way of populating ParentComponent's @ChildContent RenderFragment from the ChildComponent?

<ChildComponent>
    child content
</ChildComponent>

To produce this:

<div id="ParentComponent">
    <div id="ChildComponent">child content</div>
</div>

Don't know if I'm missing something obvious but been messing around with it for a while and have not been able to find any answers on the internet.

Edit - Alternative Solution

I've found another way of doing this that doesn't rely on using overriding the BuildRenderTree methods directly and allows you to use html/razor markup, although it does mean going around the houses. Also unsure if this is safe or performant, but putting it here for completeness sake.

ParentElement.razor

@using Microsoft.AspNetCore.Components.Rendering;

@if (!HasRendered()) {
    SetRenderedStatus();
    <div id="ParentComponent">
        RenderChild(__builder);
    </div>
}

@code {
    protected bool _hasBaseRendered = false;

    protected bool HasRendered() {
        return _hasBaseRendered;
    }

    protected void SetRenderedStatus() {
         _hasBaseRendered = true;
    }

    protected virtual void RenderChild(RenderTreeBuilder __builder) { }
}

ChildElement.razor

@using Microsoft.AspNetCore.Components.Rendering
@inherits ParentElement

@if (HasRendered()) {
    <div id="ChildComponent">
         @ChildContent
    </div>
}
@{
    base.BuildRenderTree(__builder);
}

@code {
    [Parameter] public RenderFragment ChildContent { get; set; }

    protected override void RenderChild(RenderTreeBuilder __builder) {
        this.BuildRenderTree(__builder);
    }
}

So quick explanation, the features in the IDE/Compiler clearly compile the razor/html markup portion of the code down into BuildRenderTree methods behind the scenes, which is why you can't override those methods within a .razor component.

The above code compiles down to:

ChildElement.razor

protected override void BuildRenderTree(RenderTreeBuilder __builder)
{
    if (HasRendered())
    {
        __builder.AddContent(0, "    ");
        __builder.OpenElement(1, "div");
        __builder.AddAttribute(2, "id", "ChildComponent");
        __builder.AddMarkupContent(3, "\r\n        ");
        __builder.AddContent(4, ChildContent);
        __builder.AddMarkupContent(5, "\r\n    ");
        __builder.CloseElement();
        __builder.AddMarkupContent(6, "\r\n");          
    }
    base.BuildRenderTree(__builder);
}

So the ChildContent is ignored while HasRendered is false and it is directed to call the base.BuildRenderTree on the parent class.

ParentElement.razor

protected override void BuildRenderTree(RenderTreeBuilder __builder)
{
    if (!HasRendered())
    {
        SetRenderedStatus();
        __builder.AddContent(0, "        ");
        __builder.OpenElement(1, "div");
        __builder.AddAttribute(2, "id", "ParentComponent");
        __builder.AddMarkupContent(3, "\r\n");
        RenderChild(__builder);
        __builder.AddContent(4, "        ");
        __builder.CloseElement();
        __builder.AddMarkupContent(5, "\r\n");
    }
}

It renders the parents markup and midway is directed to RenderChild which is overriden on the child, that takes the flow back up a level to the child, where its BuildRenderTree is then called, the HasRendered check will ensure an infinite loop is avoided and it then renders the childs portion of the markup before returning back down to the parent to complete.

This could probably be refactored, just my current working copy experiment.

2

2 Answers

2
votes

Copy and test...

ParentComponent.cs

public partial class ParentComponent : ComponentBase
{
    [Parameter] public RenderFragment ChildContent { get; set; }

    protected override void BuildRenderTree(RenderTreeBuilder builder)
    {
        builder.OpenElement(0, "div");
        builder.AddAttribute(1, "id", "ParentComponent");
        builder.AddContent(2, ChildContent);
        builder.CloseElement();
        
        base.BuildRenderTree(builder);
    }
}

ChildComponent.cs

public partial class ChildComponent : ParentComponent
{
    protected override void BuildRenderTree(RenderTreeBuilder builder)
    {
        builder.OpenComponent<ParentComponent>(0);
        builder.AddAttribute(1, "ChildContent", (RenderFragment)((builder2) => 
       {
            builder2.OpenElement(1, "div");
            builder2.AddAttribute(2, "id", "ChildComponent");
            builder2.AddContent(3, ChildContent);
            builder2.CloseElement();
        } ));

        builder.CloseComponent();
    
    }
}

Index.razor

@page "/"

<ChildComponent>child content</ChildComponent>
0
votes

I just tested this on 5.0 rc2.

@inherits ParentComponent

<div id="ChildComponent">@ParentRenderFragment</div>

@code {

    RenderFragment ParentRenderFragment => builder =>
    {
        builder.OpenComponent<ParentComponent>(1);
        builder.AddAttribute(2, "ChildContent", this.ChildContent);
        // Additional inherited parameter's here
        builder.CloseComponent();
    };

}

Produced this:

enter image description here

Note:

The id's are incorrect as they are passed as part of the renderfragment. If you do not have access to edit the child component this may require dom manipulation to fix as you need to capture the attributes or pass the id as a parameter.

However this is showing how the get the parents original render into the child.