[Blazor] Does @bind-{PARAMETER}:set behave differently than {PARAMETER}Changed for component parameters?
I opened this issue in the AspNetCore.Docs repository, and was told to open an issue here after some discussion.
I'm wondering if there's a functional difference between using @bind-{PARAMETER}:set and {PARAMETER}Changed when dealing with component parameters. From my testing, they appear to behave the same.
From the original issue:
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?
cc: @guardrex https://github.com/dotnet/AspNetCore.Docs/issues/36435
I'm a bot. Here is a possible related and/or duplicate issue (I may be wrong):
- https://github.com/dotnet/aspnetcore/issues/57962
Thanks for opening here, @MihuBot. I'll re-open your docs issue if more work is required after additional discussion here.
For Artak's remarks ...
Basically, with the @bind-{parameter}:set you can react to the change before it happens, and yes, change the applied value if necessary before it gets applied. Whereas with the change event registration you get the notification after the change was applied.
... I reacted on the docs PR ...
https://github.com/dotnet/AspNetCore.Docs/pull/34323/files
That content is live in the article and the most up-to-date info from the PU to date. I'm 👂 if we need to do more work on this subject.
@UniMichael, thank you for your effort to improve Blazor.
The functional difference you noticed is indeed present, it depends on the actual implementation of HTML element. The documentation linked above that describes the behavior might be a bit unclear. I would propose changing:
https://github.com/dotnet/AspNetCore.Docs/pull/34323/files
to:
The :get and :set modifiers are always used together.
Binding to HTML Elements
Recommended (.NET 7+): Use @bind:get and @bind:set modifiers to ensure the input element stays synchronized with your bound value, even when you modify the value in the handler:
<input @bind:get="_value" @bind:set="HandleValueChange" />
Legacy approach: Using value attribute with @onchange:
<input value="@_value" @onchange="HandleValueChange" />
This approach can cause synchronization bugs where the input element displays a different value than your bound variable. Migrate to @bind:get/@bind:set to avoid this issue.
Binding to Component Parameters
For component parameters, both syntaxes are functionally equivalent:
<MyComponent Value="@_value" ValueChanged="@(v => _value = v)" />
<MyComponent @bind-Value:get="@_value" @bind-Value:set="@(v => _value = v)" />
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).
If you changed the way you test - the internal HTML element would use the old approach, then you would see that both
<ThreeCharacterInput Value="@_value" ValueChanged="@(value => _value = value)" />
<ThreeCharacterInput @bind-Value:get="@_value" @bind-Value:set="@(value => _value = value)" />
would fail consistently.
cc @guardrex, let me know if this doc update sounds good. If so, let's add apply it.
Yes, thanks @ilonatommy.
I'll perform the updates and ping you for review on the PR, probably tomorrow ....... maybe 😆 ... Dan is working on the Routing and Navigation article split review, and that's a SUPER top priority for me that might delay any other work for a short time ... a day or two.
I've re-opened your original issue, @UniMichael, at #36435 to perform the work. I'll ping you on the PR, too.
UPDATE: Short delay ... I'm bogged down in other updates at the moment. It won't get lost tho. I'll probably reach it by EOW.