Clarify difference between @bind:get/@bind:set and Value/ValueChanged in component parameters binding
Description
The "Bind across more than two components" states the following:
@bind:get/@bind:setsyntax allows you to:
- Avoid creating an extra property that only exists to forward values and callbacks across chained components, which was required prior to the release of .NET 7.
- Intercept and transform values before they're applied.
- Keep the parameter immutable in the child, while still supporting two-way binding.
Its example is about how @bind:get/@bind:set avoids the need for a custom property that would handle passing the value and calling the change handler and dropping its Task (like you needed in .NET 6) if we wanted to use @bind-ChildMessage, but doesn't actually elaborate on the differences between something like this:
<MyComponent @bind-Value="Value" />
<MyComponent @bind-Value:get="@Value"
@bind-Value:set="@(value => Value = value)"/>
<MyComponent Value="@Value"
ValueChanged="@(value => Value = value)"
ValueExpression="@(() => Value)"/>
@code {
private string Value
{
get => field;
set => field = value.Length > 3 ? value[..3] : value;
} = string.Empty;
}
There is a functional difference between @onchange and @bind:set for native HTML elements like <input /> (it lets Blazor know you're planning on updating the bound value, and that it'll need to re-render), but is there a practical difference when it comes to component parameters? From my testing, there doesn't appear to be a difference (though maybe I'm missing something).
Page URL
https://learn.microsoft.com/en-us/aspnet/core/blazor/components/data-binding?view=aspnetcore-10.0#bind-across-more-than-two-components
Content source URL
https://github.com/dotnet/AspNetCore.Docs/blob/main/aspnetcore/blazor/components/data-binding.md
Document ID
3f912dfc-5ba5-6c6c-ba93-1ae78508a5cc
Platform Id
ad2f661d-d520-a055-4a4e-26b9520e6ce3
Article author
@guardrex
Metadata
- ID: b211df59-e1c9-86c0-c904-31fa3c361e6d
- PlatformId: ad2f661d-d520-a055-4a4e-26b9520e6ce3
- Service: aspnet-core
- Sub-service: blazor
🥳 Happy Holidays! 🍽️
Stand-by! A green dinosaur 🦖 will be along shortly to assist.
Thanks for the issue and questions. Perhaps, the article doesn't elaborate on the differences in one place, but the article explains how and when to use each binding strategy where each is discussed (each section) AFAIK.
Let's ask if @javiercn has any further remarks on your statements/questions.
Respectfully, I'm gonna push back slightly here (feel free to correct me if I'm wrong, I might just be missing something).
I can't find an explanation to the difference between using an EventCallback component parameter vs using the @bind:get/@bind:set syntax on a component parameter.
The earlier part of the documentation points to the section I'm writing about to get more details on how binding works with component parameters.
Like, functionally, what's the difference between something like MyParameterChanged="HandleChange" vs @bind-MyParameter:set="HandleChange"?
Chained bind with EventCallback is focused on one-way binding scenarios, while get/set modifier binding is focused on two-way binding scenarios ... and you cited the relevant explanation dealing with avoiding foul rendering effects associated with writing directly to component parameters in two-way binding scenarios of a nested component hierarchy.
I'm not sure what you mean by 'functional difference.' Do you mean framework implementation functional differences? If so, the product unit doesn't like the articles to discuss the implementations because they're subject to change without notice. What you can do is look directly at the reference source if you wish to see how the framework processes component binding for any given release.
WRT {FIELD/PROPERTY}Expression: That section has a somewhat weak example that doesn't do anything useful other than obtaining the model and field name. The section explains that it could be used for validation scenarios, and we don't show an example. I feel like more work could be done in the section ... 🤔 ... another example ... a real world example would be nice to show after the existing general example.
I'll take a closer look at these sections soon. Give me a few days tho ... I'm a bit slammed at the moment ⛰️⛏️😅. I hope to have some time early next week to analyze the coverage.
I'm not sure what you mean by 'functional difference.'
The earlier docs mention that @bind:get/@bind:set differs from something like @onchange in that it informs the framework that your intent is to modify the value. So it'll update the DOM after the handler. When dealing with component parameters, though, you're not really dealing with the DOM anymore, right?
If you'll excuse the slightly longer example:
MyPage.razor:
@page "/"
<div>@_value</div>
<input value="@_value" @onchange="HandleValueChanged" />
<input @bind:get="_value" @bind:set="HandleBoundValueChanged" />
@code
{
private string _value = string.Empty;
private void HandleValueChanged(ChangeEventArgs args) =>
HandleBoundValueChanged(args.Value?.ToString() ?? string.Empty);
private void HandleBoundValueChanged(string value) =>
_value = value.Length > 3 ? value[..3] : value;
}
With the above code, if I take the first input (old style change handling), and I type out "12345", it'll get turned into "123" and the <div> will show "123". If I then add "45" (so "12345" again), the <div> will continue showing "123", but the input will still have "12345".
The second input doesn't have this problem, because it uses @bind:get/@bind:set, and that's precisely the type of problem it solves.
I convert the "keep 3 characters in my input" logic into its own component which uses @bind:get/@bind:set:
ThreeCharacterInput.razor:
<input @bind:get="Value" @bind:set="HandleValueChangedAsync" />
@code
{
[Parameter]
public string Value { get; set; } = string.Empty;
[Parameter]
public EventCallback<string> ValueChanged { get; set; }
private async Task HandleValueChangedAsync(string value) =>
await ValueChanged.InvokeAsync(value.Length > 3 ? value[..3] : value);
}
Is there supposed to be any difference between these two uses below? From my testing, they both work the same (I can't reproduce the "12345" bug with either of them).
MyPage.razor:
@page "/"
<div>@_value</div>
<ThreeCharacterInput Value="@_value" ValueChanged="@(value => _value = value)" />
<ThreeCharacterInput @bind-Value:get="@_value" @bind-Value:set="@(value => _value = value)" />
@code
{
private string _value = string.Empty;
}
So when I ask about "functional" differences, I mean is there an inherent reason to use one version over the other when dealing with component parameters? Is the guidance to use @bind-{PARAMETER}:get/@bind-{PARAMETER}:set with component parameters just to be consistent with HTML bindings? Am I missing some subtle issue in the non @bind-based approach?
I don't think Javier saw the ping here.
I recommend opening an issue for the product unit. Probably either Mackinnon or Javier will respond there to answer your questions about this. I'll close here, but I'll monitor their response on your issue and re-open this issue for work if needed.
Please add ...
cc: @guardrex https://github.com/dotnet/AspNetCore.Docs/issues/36435
... to the bottom of your opening comment so that I can follow along.