Exploring two-way binding in Blazor

  • Gérald Barré

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 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 own 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 use 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!

Follow me:
Enjoy this blog?Buy Me A Coffee