Exploring two-way binding in Blazor
In ASP.NET Core Blazor, you may already have use two-way binding in forms. This is useful to bind a property to an input element and update the value of the property when the input value changed. To use 2-way binding you can use the @bind
directive. Here is an example of binding the Age
property to an input of type number:
<EditForm Model="model">
<InputNumber @bind-Value="model.Age" />
</EditForm>
Razor rewrites the @bind
directive to set the property Value
and add an event handler for ValueChanged
. Setting Value
defines the original value for the child component. When the value is changed in the child component, it notifies the parent using the ValueChanged
callback. The parent component can update the value of the Age
property. In short, the @bind
directive is a syntactic sugar for:
<EditForm Model="model">
@*
Value => Set the initial value
ValueChanged => Update the model when the user changes the value in the editor
*@
<InputNumber Value="model.Age" ValueChanged="newValue => model.Age = newValue" />
</EditForm>
#Creating a custom component that supports two-way binding
The @bind
directive is just a syntactic sugar. This means you can support 2-way binding in your component if you use the same pattern. For instance, you can create a custom editor for Boolean value. All you need is a Boolean parameter to set the value and another parameter of type EventCallback<bool>
with the suffix Changed
. To be clear, you need 2 parameters with the following pattern:
@code {
[Parameter]
public TValue Value { get; set; }
[Parameter]
public EventCallback<TValue> ValueChanged { get; set; }
}
Here's an example of a component with 2 buttons to select a Boolean value:
<button type="button" @onclick="() => SetValue(true)" class="@(Value ? "active" : "")">@TrueText</button>
<button type="button" @onclick="() => SetValue(false)" class="@(!Value ? "active" : "")">@FalseText</button>
@code{
[Parameter]
public string TrueText { get; set; }
[Parameter]
public string FalseText { get; set; }
[Parameter]
public bool Value { get; set; }
[Parameter]
public EventCallback<bool> ValueChanged { get; set; }
private async Task SetValue(bool value)
{
if (Value != value)
{
Value = value;
await ValueChanged.InvokeAsync(value);
}
}
}
You can use this component using the @bind-Value
directive:
<Boolean @bind-Value="agree" TrueText="Agree" FalseText="Disagree" />
<div>Selected value: @agree</div>
@code {
bool agree;
}
You can check the C# code generated for this Blazor component to validate it uses the event correctly:
protected override void BuildRenderTree(Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
{
__builder.OpenComponent<Meziantou.SampleBlazorApp.Shared.BooleanComponent>(0);
__builder.AddAttribute(1, "TrueText", "Agree");
__builder.AddAttribute(2, "FalseText", "Disagree");
// 👇 Set Value and ValueChanged
__builder.AddAttribute(3, "Value", RuntimeHelpers.TypeCheck<bool>(agree));
__builder.AddAttribute(4, "ValueChanged", RuntimeHelpers.TypeCheck<EventCallback<bool>>(
EventCallback.Factory.Create<bool>(this,
RuntimeHelpers.CreateInferredEventCallback(this, __value => agree = __value, agree))));
__builder.CloseComponent();
}
You can expand the @bind
directive by yourself and use the full syntax as follow:
<BooleanComponent TrueText="Agree" FalseText="Disagree" Value="agree" ValueChanged="newValue => agree = newValue" />
In the next blog post we'll see how to use 2-way binding in an actual component. Stay tuned!
Do you have a question or a suggestion about this post? Contact me!