I'm new to blazor and I came across MudBlazor components. These are great, so I decided to implement them in my project. What I'd like to do is wrap each MudBlazor component that I use in my own custom component so that if I ever change anything in the future it will be simple (as is already the case as I already decided to change from Radzen to MudBlazor). For the most part, this is fairly straight forward, with the one exception of the @bind-Value property. I can't seem to figure out how to get this to work. Here is my custom component wrapping the MudBlazor "MudTextField".
<MudTextField @bind-Value="@BindTo" Label="@Label" Variant="@Variant" Margin="@Margin"></MudTextField>
@code {
private string bindingValue;
[Parameter]
public string BindTo
{
get => bindingValue;
set
{
if (bindingValue == value) return;
bindingValue = value;
BindToChanged.InvokeAsync(value);
}
}
[Parameter] public EventCallback<string> BindToChanged { get; set; }
[Parameter] public string Label { get; set; }
[Parameter] public Variant Variant { get; set; } = Variant.Outlined;
[Parameter] public Margin Margin { get; set; } = Margin.Dense;
}
This isn't working. When I set the BindTo parameter when calling my custom component, I can see the code in the set property being called, and the bindingValue is being set properly, but the property on the object I'm binding to isn't being updated. Do I need to do something else with the BindToChanged parameter? I saw that used in another example while researching the issue, but I don't really understand what it's supposed to do. Any help would be appreciated.
Thanks!
You need to pass through the triumverate of Value
, ValueChanged
and ValueExpression
on to the MudBlazor component.
I don't work with MudBlazor so can't test this properly, and I only know how to build this as a RenderFragment rather than in Razor, but something like this should work:
public class MyMudBlazorTextField<TValue> : ComponentBase
{
[Parameter]
public TValue? Value { get; set; }
[Parameter] public EventCallback<TValue> ValueChanged { get; set; }
[Parameter] public Expression<Func<TValue>>? ValueExpression { get; set; }
[Parameter] public string Label { get; set; }
[Parameter] public Variant Variant { get; set; } = Variant.Outlined;
[Parameter] public Margin Margin { get; set; } = Margin.Dense;
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
builder.OpenComponent<MudTextField<TValue>>(0);
builder.AddAttribute(1, "Variant", this.Variant);
builder.AddAttribute(2, "Margin", this.Margin);
builder.AddAttribute(3, "Label", this.Label);
builder.AddAttribute(4, "Value", this.Value);
builder.AddAttribute(5, "ValueChanged", EventCallback.Factory.Create(this, this.ValueChanged));
builder.CloseComponent();
}
}
You then use the normal @bind-value
<MyMudBlazorTextField @bind-value=myfield ...../>
You're probably making a lot of effort for nothing. Things like Variant and Margin as all specific to MudBlazor, and MudBlazor has some specific ways of building things, so unless you fix these in your components, you're muddying the waters on your generic components (to excuse a pun)!
I've lifted some code from the Account
page on the MudBlazor Dashboard project. It sets the Text field with this function:
public static void CreateMudTextField_0<T>(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder, int seq, int __seq0, global::System.String __arg0, int __seq1, global::MudBlazor.Variant __arg1, int __seq2, T __arg2, int __seq3, global::Microsoft.AspNetCore.Components.EventCallback<T> __arg3)
{
__builder.OpenComponent<global::MudBlazor.MudTextField<T>>(seq);
__builder.AddAttribute(__seq0, "Label", __arg0);
__builder.AddAttribute(__seq1, "Variant", __arg1);
__builder.AddAttribute(__seq2, "Value", __arg2);
__builder.AddAttribute(__seq3, "ValueChanged", __arg3);
__builder.CloseComponent();
}
Base on this I've made a couple of adjustments to the code.