I'm seeing this common pattern in some libraries (MatBlazor, Telerik) of having ValueChanged
and ValueExpression
properties and it really confuses me.
What is the difference between both? And when to use it?
Actually, you've forgotten the third element of this pattern: Value
. This "trinity" of properties is frequently used for component two-way data binding. Notably, these properties are employed inside the built-in Blazor form components, such as <InputText>
.
Let's look at an example:
<InputText @bind-Value="employee.FirstName" />
Value
is a property provided in the form of @bind-Value="model.PropertyName"
.
ValueChanged
is of type EventCallback<TValue>
. It stands for a callback that updates the bound value. As you can see, we do not use it in the above example—it's not necessary. The compiler knows its job and it takes care of this, meaning that it adds an EventCallback
"delegate" with all the necessary settings behind your back.
ValueExpression
, finally, refers to an expression that identifies the bound value. It is automatically created by the compiler, and you rarely, if ever, have to set it.
Now let's compare the above with the code below. The following example creates a two-way data binding between a parent component and a child component. However, instead of using the standard "trinity" (Value
, ValueChanged
, ValueExpression
), we will replicate the underlying pattern for ourselves:
ParentComponent.razor:
<ChildComponent @bind-Text="FirstName" />
@code {
[Parameter]
public string FirstName { get; set; }
}
ChildComponent.razor:
<input @bind="Text" />
@code {
private string text;
[Parameter]
public string Text
{
get { return text; }
set
{
if (text != value) {
text = value;
if (TextChanged.HasDelegate)
{
TextChanged.InvokeAsync(value);
}
}
}
}
[Parameter]
public EventCallback<string> TextChanged { get; set; }
}
The built-in <InputText>
and our custom <ChildComponent>
are basically the same!
To answer your other question...
When will I use
ValueChanged
andValueExpression
in Blazor?? I'm creating a wrapper of an input from another library, is this a case for using this trinity?
As explained above, ValueChanged
and ValueExpression
are properties defined in Blazor's built-in components, and most of the time you won't need to use them directly.
Look again at the two components I've defined above: <ParentComponent>
and <ChildComponent>
. Change Text
and TextChanged
to Value
and ValueChanged
, and my components are still valid and work correctly. The only difference is in naming. What do I do in the <ChildComponent>
? I define a parameter property named Text
(stands for Value
). As I want to enable two-way data binding between the parent and child components, I also need to define a parameter property called here TextChanged
(stands for ValueChanged
). Text
goes to TextChanged
, Value
goes to ValueChanged
, and Year
goes to YearChanged
. The naming is only convention. The main point is that you have to define a property and an EventCallback
of the same data type as the property.
Inside the parent component I provide the property as follows:
<ChildComponent @bind-Text="NameOfAPropertyDefinedInTheParentComponent" />
or <ChildComponent @bind-Value="NameOfAPropertyDefinedInTheParentComponent" />
or <ChildComponent @bind-Year="NameOfAPropertyDefinedInTheParentComponent" />
In my components above, there is also code, as for instance in the child component, that invokes the TextChanged
delegate in order to pass a value back to the parent component; this is exactly what the ValueChanged
delegate does in the components in which it is defined. But you as a user do not have to use it. Look at my components... They work perfectly well. No need to touch. If you as a user of my component want to subclass it, then you need to know what you're doing and how to subclass a Blazor component properly. But my components, partially presented here, are relatively simple.
Suppose you want to create a password input based on <InputText>
, which is not only doable but quite easy. In that case, you're not going to change anything but the look of the <InputText>
component so that asterisk symbols are displayed instead of normal text. The rest of the component is unchanged. You do not need to handle the events and such. This, of course, does not mean that a component author will never need to call the EventCallback
from somewhere in his code. That said, I have never had a good reason to trigger the ValueChanged
delegate when using the <InputText>
component. And I only once had to provide a ValueExpression
, as the compiler was not able to identify the bound value. (I'll look for it, and if found I'll post it here...)