Blazor - What is the correct way to extend another component?

1.6k Views Asked by At

I'm using MudBlazor component library. In order to show loading on form buttons, the documentation guides like this:

<MudButton Disabled="@_processing" OnClick="ProcessSomething" Variant="Variant.Filled" Color="Color.Primary">
    @if (_processing)
    {
        <MudProgressCircular Class="ms-n1" Size="Size.Small" Indeterminate="true"/>
        <MudText Class="ms-2">Processing</MudText>
    }
    else
    {
        <MudText>Click me</MudText>
    }
</MudButton>

Now since I'm doing this a lot, I wanted to wrap this logic inside another component.

The following component does not do the job:

@inherits MudButton

@code {
    bool _loading;

    [Parameter]
    public bool Loading 
    {
        get => _loading;
        set
        {
            _loading = value;
            Disabled = value;
        }
    }

    [Parameter]
    public new RenderFragment ChildContent 
    {
        get => base.ChildContent;
        set => base.ChildContent = ExtendContent(value);
    }

    private RenderFragment ExtendContent(RenderFragment baseContent) => __builder =>
    {
        if (Loading)
        {
            <MudProgressCircular Class="ms-n2" Size="Size.Small" Indeterminate="true" />
        }
        @baseContent
    };
}

I get this error:

The type '<my_component>' declares more than one parameter matching the name 'childcontent'. Parameter names are case-insensitive and must be unique.

3

There are 3 best solutions below

0
Brian Parker On BEST ANSWER

Create a component for the loading content:

ButtonLoadingContent.razor

@using MudBlazor
<MudProgressCircular Class="ms-n1" Size="Size.Small" Indeterminate="true" />
<MudText Class="ms-2">Processing</MudText>

BaseLoadingMudButton.cs

using MudBlazor;

public class BaseLoadingMudButton<TComponent> : MudButton
    where TComponent : ComponentBase
{
    protected override void OnParametersSet()
    {
        Disabled = Loading;

        if (Loading is true)
        {
            ChildContent = builder =>
            {
                builder.OpenComponent<TComponent>(sequence: 1);
                builder.CloseComponent();
            };
        }
        base.OnParametersSet();
    }

    [Parameter]
    public bool Loading { get; set; }
}

LoadingMudButton.cs

public class LoadingMudButton : BaseLoadingMudButton<ButtonLoadingContent> { }

Usage

<LoadingMudButton Loading=_processing OnClick="ProcessSomething" Variant="Variant.Filled" Color="Color.Primary">
    <MudText>Click me</MudText>
</LoadingMudButton>

enter image description here enter image description here

Why does it work? I didn't use .razor files for the inheritance. The base component does not have to use generics I included this to make it more flexible and as an example.

0
buga On

MudButton already has ChildContent Parameter, you don't need to inherit it, Just use it inside your new component, and redefine the parameters you need to pass them to the inner components

2
MrC aka Shaun Curtis On

MudBlazor's component library inherits from ComponentBase which isn't really designed for Razor inheritance. You're trying to replace the ChildContent with your own markup.

You need to lift the markup stuff from the base component, hope there's no privates being used, and copy it into the child markup.

Here's my inherited component:

@inherits MudButton
@using MudBlazor.Extensions

<MudElement @bind-Ref="@_elementReference"
            HtmlTag="@HtmlTag"
            Class="@Classname"
            Style="@Style"
            @attributes="UserAttributes"
            @onclick="OnClickHandler"
            type="@ButtonType.ToDescriptionString()"
            href="@Href"
            target="@Target"
            rel="@(Target=="_blank"?"noopener":null)"
            disabled="@Disabled">

    <span class="mud-button-label">
        @if (!string.IsNullOrWhiteSpace(StartIcon))
        {
            <span class="@StartIconClass">
                <MudIcon Icon="@StartIcon" Size="@Size" Color="@IconColor" />
            </span>
        }

        @if (this.Loading)
        {
            <MudProgressCircular Class="ms-n1" Size="Size.Small" Indeterminate="true" />
            <MudText Class="ms-2">Processing</MudText>
        }
        else
        {
            <MudText>@ChildContent</MudText>
        }

        @if (!string.IsNullOrWhiteSpace(@EndIcon))
        {
            <span class="@EndIconClass">
                <MudIcon Icon="@EndIcon" Size="@Size" Color="@IconColor" />
            </span>
        }
    </span>

</MudElement>

@code {
    [Parameter] public bool Loading { get; set; }
}

The code comes from the MubBlazor Github repo here - https://github.com/MudBlazor/MudBlazor/blob/dev/src/MudBlazor/Components/Button/MudButton.razor.

My Demo Page:

@page "/"

<PageTitle>Index</PageTitle>

<MudText Typo="Typo.h3" GutterBottom="true">Hello, world!</MudText>
<MudText Class="mb-8">Welcome to your new app, powered by MudBlazor!</MudText>
<MudAlert Severity="Severity.Normal">You can find documentation and examples on our website here: <MudLink Href="https://mudblazor.com" Typo="Typo.body2" Color="Color.Inherit"><b>www.mudblazor.com</b></MudLink></MudAlert>

<MudText class="mt-6">

    <MudButton Disabled="@_processing" OnClick="ProcessSomething" Variant="Variant.Filled" Color="Color.Primary">
        @if (_processing)
        {
            <MudProgressCircular Class="ms-n1" Size="Size.Small" Indeterminate="true" />
            <MudText Class="ms-2">Processing</MudText>
        }
        else
        {
            <MudText>Click me</MudText>
        }
    </MudButton>

    <MyButton Loading=_processing Disabled="@_processing" OnClick="ProcessSomething" Variant="Variant.Filled" Color="Color.Primary">
        Hello
    </MyButton>

</MudText>
@code {
    private bool _processing;

    private async Task ProcessSomething()
    {
        _processing = true;
        await Task.Delay(5000);
        _processing = false;
    }
}

enter image description here