Blazor: Chained Binds

A chained bind is binding a custom child component's property to a property of the parent component.

Maybe you've already discovered how to two-way data bind an input element. That works with the @bind syntax to do a two-way databinding between the value of the input and a property of the parent component.

An example:

<input type="date" @bind="[PropertyName]" />

[PropertyName] here is the name of a property present in the parent, the component that renders the input. It can also be a property of some object instance. For example if there is an object called customer you can use customer.BirthDate as the [PropertyName].

But what if we want to give the date input a little bit of intelligence. Is has to render a button next to it. When the user selects a different date and presses the button the value should revert to the original date.

It makes sense to create a new component with this functionality. Let me introduce you to the new DateField component. It uses bootstrap and font awesome:

<input type="date" value="@Date?.ToString(dateFormatString)"
       format-value="@dateFormatString" @onchange="OnDateChanged" />
 
<button class="btn btn-sm" @onclick="Revert">
    <i class="fas fa-undo"></i>
</button>
 
@code {
    private string dateFormatString = "yyyy-MM-dd";
    private DateTime? origDate;
 
    [Parameter]
    public DateTime? Date { getset; }
 
    [Parameter]
    public EventCallback<DateTime?> DateChanged { getset; }
 
    private async Task Revert()
    {
        if (Date != origDate)
        {
            Date = origDate;
            await DateChanged.InvokeAsync(origDate);
        }
    }
 
    public async Task OnDateChanged(ChangeEventArgs e)
    {
        var date = (string)e.Value;
        if (DateTime.TryParse(dateout DateTime newDate))
        {
            Date = newDate;
            await DateChanged.InvokeAsync(newDate);
        }
    }
 
    protected override void OnInitialized()
    {
        origDate = Date;
    }
}


It still has an input element of type date. But we can't use the @bind syntax we used before because when the value changes we want to do more than just update the bound property. I'll come back to that in a second. Because we can't use @bind I bind the value attribute manually to a property called Date.
An input element doesn't work with DateTime objects, just strings. So I'm doing a conversion here.
Here's another question you might have: The HTML input I showed earlier can be bound directly to a property of the type DateTime. And that works without using a conversion. That's because the @bind functionality of Blazor sees that the data binding is done with a date input and automatically takes care of the conversion. That doesn't work with our custom component.


Back to the DateField component: When the date is changed, the onchange event fires that triggers the OnDateChanged method.
In OnDateChanged we get the new value by using ChangeEventArgs.
We convert the string to a DateTime object and assign it to the Date property. We want the parent component to bind to this property so it's marked with parameter.
The last line, that's the extra thing we need to do, is invoking an EventCallback that notifies the parent of the fact that the date has changed. We need that because we want to support two-way databinding.

Now for the Revert functionality there is a method called Revert that fires when somebody clicks the button.
When the date is different the DateChanged eventhandler is fired with the original date. Which again sets the Date property. The origDate property is set when the component initialized.

It's time to use the DateField component. We can't just use @bind because that is specifically to bind the value property of an input element. Because DateField is a custom component we need a way to explicity bind to the Date property. We can do that by typing @bind-Date followed by a field or property we want to bind to.

<DateField @bind-Date="[PropertyName]" />

Where [PropertyName] is some DateTime property.
There's now a two-way databinding in place between the Date property in DateField and the EndDate property of the benefit. So when one changes, the other one changes.
To make two-way databinding work we need an EventCallback to notify the parent when the date value changes like we did in DateField. And we have to follow a naming convention for the EventCallback. It must be called [PropertyName]Changed. So in this case the property this callback is for is named Date. When the parent uses the @bind-Date syntax Blazor will automatically look for an EventCallback property called DateChanged and subscribe to it so that the EndDate property in employee gets updated when the Date property value changes.


The built-in form components use chained binds as well.
For example:

<InputText @bind-Value="@[PropertyName]" </InputText>

The InputText component has a property called Value to type string. It also has an EventCallback property called ValueChanged. The other Blazor form components (InputDate, InputNumber etc.) all have a Value property and a ValueChanged EventCallback property. The difference is that they don't use a string but another type relevant for the type of input.

ASP.NET Core
Tweet Post Share Update Email RSS