I have a fairly simple component, which looks like this:
<form><div class="form-group"><label for="sampleText">Sample Text</label><input type="text" class="form-control" id="sampleText" aria-describedby="sampleTextHelp" placeholder="Enter a text" @bind="@Value" /><small id="sampleTextHelp" class="form-text text-muted">Enter a text to test the databinding</small></div></form><p>You entered here: @Value</p>@code { [Parameter] public string Value { get; set; }}I now add this to a page like this:
<MVVMTest Value="@_value" /><p> You entered in MVVMTest: @_value</p>@functions { private string _value;}When I enter text in the input field, it is correctly updated at You entered here: but not propagated to *You entered in MVVMTest":
What do I have to do to get this correctly propagated?
I could think of hooking up an Action<string> as a second [Parameter] which I fire inside the component when the text is changed, but it seems like a hackish and roundabout way. Is it how it has to be done, or is there a better way?
Followup 1
The answer from Issac does not work because it stumbles over @bind-value:oninput="@((e) => ValueChanged.Invoke(e.Value))" with Cannot convert lambda expression to type 'object' because it is not a delegate type.
I had to do it in this roundabout way:
<form><div class="form-group"><label for="sampleText">Sample Text</label><input type="text" class="form-control" id="sampleText" aria-describedby="sampleTextHelp" placeholder="Enter a text" @bind="@Value" /><small id="sampleTextHelp" class="form-text text-muted">Enter a text to test the databinding</small></div></form><p>You entered here: @Value</p>@code { private string _value; [Parameter] public string Value { get => _value; set { if(Equals(value, _value)) { return; } _value = value; OnValueChanged.InvokeAsync(value).GetAwaiter().GetResult(); } } [Parameter] public EventCallback<string> OnValueChanged { get; set; }}And using it like:
@inject HttpClient http@page "/test"<div class="top-row px-4"> Test Page</div><div class="content px-4"><MVVMTest Value="@_value" OnValueChanged="@(v => _value = v)" /><p> You entered in MVVMTest: @_value</p></div>@functions { private string _value;}Not pretty, but it works. My "real" component is more complicated as a changed value triggers calls to the underlying ASP.net Core Service so I have to do elaborate detection who changes what to avoid infinite loops.
It would surely be better if Blazor supported XAML/WPF-like MVVM though...
Followup 2
I came back to Issac's solution and with an extra cast, I got it working:
<form><div class="form-group"><label for="sampleText">Sample Text</label><input type="text" class="form-control" id="sampleText" aria-describedby="sampleTextHelp" placeholder="Enter a text" value="@Value" oninput="@((Func<ChangeEventArgs, Task>)(async e => await OnValueChanged.InvokeAsync(e.Value as string)))" /><small id="sampleTextHelp" class="form-text text-muted">Enter a text to test the databinding</small></div></form><p>You entered here: @Value</p>@code { private string _value; [Parameter] public string Value { get; set; } [Parameter] public EventCallback<string> OnValueChanged { get; set; }}Usage:
<MVVMTest Value="@_value" OnValueChanged="@(v => _value = v)" /><p> You entered in MVVMTest: @_value</p>@functions { private string _value;}This is already a known issue in Blazor. I tripped over this last week already, and I posted the workaround in the VS developer forum as well.
