Search code examples
checkboxdata-bindingbindingblazor2-way-object-databinding

Difference between Value and @bind-Value?


I am looking at the docs for an InputCheckBox https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.forms.inputcheckbox, and I see that it exposes Value to be bound to a desired boolean (Gets or sets the value of the input. This should be used with two-way binding.). Nonetheless, everywhere, people are using @bind-Value instead, Moreover, I cannot get Value to work.

How is this:

<InputCheckbox @bind-Value="model.IsSelected"></InputCheckbox>

Different from this (And why this one does not work):

<InputCheckbox Value="@model.IsSelected"></InputCheckbox>

I also noted that @bind-Value updates/notifies the model about the changes and updates any property that depends on IsSelected, while Value does not (probably unless explicitly specified?). Additionally, when using Value, I need to also include a ValueExpression for the tag (or it won't render). What this ValueExpression?? In which scenarios would someone implement a different ValueExpression?

Has using Value any benefit? What will it take to make it work? Am I missing something here?


Solution

  • Some more background information and an explanation of InputBase.

    All InputBase inherited components implement three Parameters:

    1. Value is the "in" value for the control - it's strongly typed.
    2. ValueChanged is the "out" value for the control: A callback with a strongly typed value.
    3. ValueExpression is a Func delegate that defines the actual model object/property. It's used internally to create a FieldIdentifier object, which is used to identify the property in the EditContext and ValidationStore.

    This page demonstrates two ways of setting up the bind.

    The first does it manually and hooks up the change to a callback method. You use this when you have other code you want to run in addition to just setting the value. (I'm setting a time stamp).

    The second uses "syntatic sugar" provided by Razor. @bind-Value tells the Razor compiler to build out a set of code to link the three Parameters with a common name of Value to te provided model property.

    @page "/"
    
    <PageTitle>Index</PageTitle>
    
    <h1>Hello, world!</h1>
    
    <InputCheckbox class="form-check"
                   @bind-Value=this.model.Value />
    
    <InputCheckbox class="form-check"
                   Value=this.model.Value
                   ValueChanged=this.OnValueChanged
                   ValueExpression="() => this.model.Value" />
    
    <div class="alert alert-info">
        Value: @this.model.Value
    </div>
    
    <div class="alert alert-info">
        @this.message
    </div>
    
    @code {
        private Model model = new();
        private string message = "No Message";
    
        private Task OnValueChanged(bool value)
        {
            this.model.Value = value;
            // You can do other stuff here if you need to
            this.message = $"Set at {DateTime.Now.ToLongTimeString()}";
            return Task.CompletedTask;
        }
    
        public class Model
        {
            public bool Value { get; set; }
        }
    }
    

    In the compiled low level C# code they are virtually the same thing.

    Here's the full bind:

    private RenderFragment FirstComponent => __builder =>
    {
        __builder.OpenComponent<InputCheckbox>(5);
        __builder.AddAttribute(6, "class", "form-check");
        __builder.AddAttribute(7, "Value", RuntimeHelpers.TypeCheck<Boolean>(this.model.Value));
        __builder.AddAttribute(8, "ValueChanged", RuntimeHelpers.TypeCheck<EventCallback<Boolean>>(EventCallback.Factory.Create<Boolean>(this, RuntimeHelpers.CreateInferredEventCallback(this, __value => this.model.Value = __value, this.model.Value))));
        __builder.AddAttribute(9, "ValueExpression", RuntimeHelpers.TypeCheck<global::System.Linq.Expressions.Expression<System.Func<System.Boolean>>>(() => this.model.Value));
        __builder.CloseComponent();
    };
    

    Here's the manual bind:

    private RenderFragment SecondComponent => __builder =>
    {
        __builder.OpenComponent<InputCheckbox>(11);
        __builder.AddAttribute(12, "class", "form-check");
        __builder.AddAttribute(13, "Value", RuntimeHelpers.TypeCheck<global::System.Boolean>(this.model.Value));
        __builder.AddAttribute(14, "ValueChanged", RuntimeHelpers.TypeCheck<EventCallback<Boolean>>(EventCallback.Factory.Create<Boolean>(this, this.OnValueChanged)));
        __builder.AddAttribute(15, "ValueExpression", RuntimeHelpers.TypeCheck<Expression<System.Func<System.Boolean>>>(() => this.model.Value));
        __builder.CloseComponent();
    };
    

    Net7.0 @input-value:get and @input-value:set

    Net7.0 implements more syntatic sugar to let you do binding in yet another way. It also adds a third @input-value:after bind to provide a method to do the timestamp I showed above.

    See this for the latest bind information - https://learn.microsoft.com/en-us/aspnet/core/blazor/components/data-binding