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?
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...
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.
You need to add an EventCallback parameter to do that:
@code {
[Parameter]
public string Value { get; set; }
[Parameter]
public EventCallback<string> ValueChanged { get; set; }
}
read the section Binding with component parameters.