Difference between Value and @bind-Value?

877 Views Asked by At

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?

2

There are 2 best solutions below

3
MrC aka Shaun Curtis On BEST ANSWER

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

6
Henk Holterman On

As you noticed, @bind-Value delivers 2-way binding for Value.

I made the following demo once:

  • Start a new Project from one of the templates

  • Change the Counter Page a little:

<p role="status">Current count: @Count</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    [Parameter]
    public int Count { get; set; } = 1;

    [Parameter]
    public EventCallback<int> CountChanged { get; set; }

    public Task IncrementCount()
    {
        return CountChanged.InvokeAsync(Count + 1);        
    }
}
  • And then on the the Index page:
@page "/"
<div>
    <p>Counter1 : @count1</p>
    <p>Counter2 : @count2</p>
</div>

<Counter @bind-Count="count1"  />

<Counter Count="count2" CountChanged="UpdateCount2" />

@code {
    int count1 = 1;
    int count2 = 2;

    void UpdateCount2(int newValue)
    {
        count2 = newValue;
    }
}

When you now click the buttons on the Index page both Counter instances behave exactly the same.

This is because for @bind-Count the Razor compiler generates the UpdateCount1 part (equivalent) behind the scenes. And that only works when there is a callback parameter with the right name: CountChanged.

See Blazor data binding