'basic component layout inheritance blazor

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.



Solution 1:[1]

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";
    }
}

Solution 2:[2]

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"
    }
}

Solution 3:[3]

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>

Solution 4:[4]

It will never work in the way you're showing because setting a property on a parent component from an inner one doesn't trigger the rendering. AFAIK the right approach to this problem is to have an independent service class that holds some common state (in your case, the current page title), and inject it on every component that needs to change it, by meaning of event actions:

That would be something like this:

public class GlobalUtilities
{
    public string MainPageTitle { get; private set; } = "Home (default value)";

    public event Action OnMainTitleChanged;

    public void SetMainTitle(string text)
    {
        MainPageTitle = text;
        OnMainTitleChanged.Invoke();
    }
}

Register it on the pipeline in Startup.cs:

services.AddScoped<UtilsGlobales>();

it should be registered as scoped, since it's lifetime is paired with the current connection.

And then use it on all the descendant components you need:

Component

//No need to inherit, only inject...
// @inherits BaseComponent;

@inject GlobalUtilities gu

<p>Something here</p>

@code {
    protected void OnInitialized()
    {
        gu.SetMainTitle("Whatever you want");
    }
}

Hope it helps!

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Steve W
Solution 2
Solution 3 jkdba
Solution 4 Vi100