basic component layout inheritance blazor
Asked Answered
S

6

24

Let's say most of my components have a header. I want to create a base component that has the header variable and make all the other components inherit from that component and set the header. So I have

BaseComponent

@inherits LayoutComponentBase;

<h1>@header</h1>

@Body

@code {

    protected string header;
}

Component

@inherits BaseComponent;

"internal component text"

@code {
  protected override void OnInitialized()
    {
         base.header = "Setting header for the parent"
    }
}

This compiles and shows up with no errors but the base header doesn't show up. It's like anything in the base doesn't get rendered. What am I doing wrong?

P.S.

I have also tried @layout BaseComponent, and even both directives at the same time.

Sequela answered 30/1, 2020 at 16:54 Comment(1)
To solve this you'll need to learn the Blazor Component Model and the mechanism of layout in Blazor. No Shortcuts...Adaxial
M
3

2022 Update - This approach works fine for me in .NET 6.

Base Component:

<h1>@Header</h1>

@code{
    public string? Header {get; set;}
}

Derived Component:

@inherits BaseComponent

@code {
    protected override void OnParametersSet()
    {
        base.Header = "New Header Value";
    }
}
Methodist answered 9/5, 2022 at 16:30 Comment(1)
This answer seems to not the question and is a code sample with no explanation.Proportioned
D
41

As of this writing, derived razor components implement all methods of their base classes automatically, including BuildRenderTree (which renders the HTML markup that you type within the razor file). When you type nothing, that method will make no attempt at calling the base BuildRenderTree method on its own. So you need to do it manually like so:

@inherits BaseComponent;

@{
    base.BuildRenderTree(__builder);
}

@code {
  protected override void OnInitialized()
    {
         base.header = "Setting header for the parent"
    }
}
Desideratum answered 20/8, 2020 at 4:56 Comment(7)
where is the __builder coming from?Everetteverette
This answer needs updating - it's possible that blazor has moved on since; __builder does not existAttorn
@JonBarker I just tried (Blazor 7) and it works. However the IDE doesn't show it as working, but it actually builds successfully (and works)Sherrisherrie
Also works fine for me @JonBarker. I use it extensively.Alsatian
ahhh so refreshing. This is a pretty straight forward answer, thanksDonetta
If you don't want to change the base component's HTML, you should just make a .cs file and extend the component that way (this will leave its Razor/HTML intact by default).Stephniestepladder
Also, according to the Razor dev team, you should not use __builder directly. You should use a lambda that takes a builder as a parameter, because __builder is generated code and its name can change in different contexts (like nested components, render fragments, etc.) You should use @((RenderTreeBuilder builder) => base.BuildRenderTree(builder)) instead. See this GitHub issue for more details.Stephniestepladder
P
20

Expanding on @sw1337 answer, if the derived component has no markdown you can create it as a regular cs file rather than a razor file and it will work as expected without the need to call the base BuildRenderTree method. See this github for more details.

Simple Example

Base Component (MyAbstractComponent.razor)

<h1>@header</h1>

@code {
    protected string header;
}

Derived Component

public class MyDerivedComponent : MyAbstractComponent
{
    protected override void OnInitialized()
    {
         header = "set header from derived component";
    }
}

Parameter Example

Base Component (MyAbstractComponent.razor)

<h1>@Header</h1>

@code {
  [Parameter] public string Header { get; set; }
}

Derived Component

public class MyDerivedComponent : MyAbstractComponent
{
    protected override void OnParametersSet()
    {
        // coalesce
        Header ??= "Default Header from derived Component"
    }
}

Usage 1 - Default Header From Derived Component

Some Razor File

<MyDerivedComponent></MyDerivedComponent>

Result

<h1>Default Header from derived Component</h1>

Usage 2 - Specified Header From Derived Consumer

Some Razor File

<MyDerivedComponent Header="MyHeader"></MyDerivedComponent>

Result

<h1>MyHeader</h1>

RenderFragment Parameter Example

Blazor Fiddle

MyBaseComponent.razor

<h3>@Header</h3>

@if (ChildContent is not null)
{
    @ChildContent
}

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

MyDerivedComponent.cs

public class MyDerivedComponent : MyBaseComponent
{
    protected override void OnParametersSet()
    {
        // coalesce
        Header ??= "Derived Component Header";
        ChildContent ??= CreateDefaultChildContent();
    }
        
    private static RenderFragment CreateDefaultChildContent()
    {
        return builder =>
        {
            builder.OpenElement(0, "h4");
            builder.AddContent(1, "Derived Component default child content value");
            builder.CloseElement();
        };
    }
}

Usage MyRazorPage.razor

Example One Consumer passes no parameters. Default values for Header and ChildContent from derived component are used.

code

<MyDerivedComponent/>

output

<h3>Derived Component Header</h3>
<h4>Derived Component default child content value</h4>

Example Two Consumer passes Header parameter, default ChildContent of derived component is used.

code

<MyDerivedComponent Header="Consumer Header"/>

output

<h3>Consumer Header</h3>
<h4>Derived Component default child content value</h4>

Example Three Consumer passes both Header and ChildContent parameters.

code

<MyDerivedComponent Header="Consumer Header">
    <h4>Child Content - from consumer</h4> 
</MyDerivedComponent>

output

<h3>Consumer Header</h3>
<h4>Child Content - from consumer</h4>
Proportioned answered 27/1, 2022 at 17:53 Comment(1)
This answer is exactly what I needed. The example of use in razor was what I was looking for.Ammon
H
6

The following worked for me:

The Razor compiler works quite simply in my eyes, in that it always overwrites a "BuildRenderTree" method. It does not matter whether it is derived from ComponentBase, IComponent or another type. Since we cannot get the Razor compiler to generate a method name other than "BuildRenderTree" to include in our base class, we simply declare this method anew with the "new" keyword.

The only thing is that it requires an additional class in between that can redeclare the method signature.

BaseComponentCore.razor

<h1>@Header</h1>
@Body

@code
{
    protected virtual string Header => "Default";
    private protected virtual RenderFragment Body => null;
}

BaseComponent.cs

public abstract class BaseComponent : BaseComponentCore
{
    private protected sealed override RenderFragment Body => BuildRenderTree;

    // Allow content to be provided by a .razor file but without 
    // overriding the content of the base class
    protected new virtual void BuildRenderTree(RenderTreeBuilder builder)
    {
    }
}

DerivedComponent.razor

@inherits BaseComponent

<p>Hello world!</p>

@code
{
    protected override string Header => "Derived component";
}

Result

The result should be

<h1>Derived component</h1>
<p>Hello world!</p>

So internally ComponentBase still renders ComponentBase.BuildRenderTree (which is overriden in BaseComponentCore) while we can now integrate our new member BaseComponent.BuildRenderTree as a render fragment into the base class.

Helvetia answered 16/10, 2023 at 8:17 Comment(0)
M
3

2022 Update - This approach works fine for me in .NET 6.

Base Component:

<h1>@Header</h1>

@code{
    public string? Header {get; set;}
}

Derived Component:

@inherits BaseComponent

@code {
    protected override void OnParametersSet()
    {
        base.Header = "New Header Value";
    }
}
Methodist answered 9/5, 2022 at 16:30 Comment(1)
This answer seems to not the question and is a code sample with no explanation.Proportioned
S
2

According to this GitHub issue with the Razor team, you should not use __builder directly (like the accepted answer and other Google search results suggest).

__builder is an implementation detail of the Razor code generator system and its name can actually change depending on the context (i.e. nested components might name their internal builder __builder2, __builder3, etc.)

When this happens, you get an error related to an undefined parentNode object, which can be really hard to debug because it doesn't give you a lot to go on.

Instead, you can pass a function that takes a RenderTreeBuilder as a parameter and use that one explcitly (so you no longer depend on the name of the builder):

@inherits Foo

@((RenderTreeBuilder builder) => base.BuildRenderTree(builder))

@code {
    // ...
}

The above will work everywhere (nested components, inside RenderFragments, etc.)

Note: The contents of the .razor file get converted into your component's BuildRenderTree() method. If you want to use your base component's HTML as-is (without any changes), you should just make a .cs file and extend the component that way.

Stephniestepladder answered 19/6 at 14:2 Comment(0)
S
1

ReDiet's Code worked for me. I had an additional need. I created a BasePage and wanted to force every derived page to implement the PageTitle attribute.

BasePageCore.razor

@inject ICasaJsApiService _casaJsApiService

<div>
  @Body
</div>

@code{
    private protected virtual RenderFragment Body => null!;

    protected override void OnInitialized()
    {
        base.OnInitialized();
        _myJsService.setHtmlTitle(PageTitleInternal);
    }

    internal string PageTitleInternal { get; set; } = "NO TITLE SET";
}

BasePage.cs

public abstract class BasePage : BasePageCore
{
    private protected sealed override RenderFragment Body => BuildRenderTree;
    protected abstract string PageTitle { get; set; }

    protected override void OnInitialized()
    {
        PageTitleInternal = PageTitle;
        base.OnInitialized();
    }

    // Allow content to be provided by a .razor file but without 
    // overriding the content of the base class
    protected new virtual void BuildRenderTree(RenderTreeBuilder builder)
    {
    }
}

Derived components now get an error when they do not implement the abstract property PageTitle. This may be useful for someone else.

Southernly answered 17/5 at 12:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.