/ Blazor

Blazor custom component with two way databinding

This article is written for blazor 0.4. The things here are most certainly to change in the future. Blazor is all about components, so the first thing to try is create your own. If you are only displaying data everything will work out for you, but that changes when your control needs to have two way databinding.

If you follow the documentation here (which uses a readonly component): https://blazor.net/docs/components/index.html#data-binding you will end up with something like this:

mycomponent.cshtml:

@using Microsoft.AspNetCore.Blazor.Components

<input type="text" value="@Value" onchange="@valchange"/>

@functions
{
    [Parameter]
    private string Value { get; set; }

    [Parameter]
    private Action<string> ValueChanged { get; set; }
    
    protected void valchange(UIChangeEventArgs e)
    {
        Value = (string)e.Value;
        ValueChanged(Value);
    }
 }

Now if you use this component like this:

<input bind=@myParentValue"/>
<mycomponent bind-Value="@myParentValue"/>


@functions
{
    private string myParentValue { get; set; }
}

All will seems to work, if you type in the first textbox the text will automatically ends up in the second textbox. But the other way around it won't... well, it seems not to work.. secretly it is working, but not displaying the results. See the github issue I filed for this: Blazor github issue #610

Steve Sanderson suggests a workaroud in this issue, if you follow that you will end up with a lot of extra boilerplate code in your page that uses the component(s) if you have a lot of components.

Now the only reason that the results are not showing is the fact that StateHasChanged() is not called.
So a simpler workaround is to create one statefull class with an event that allows pages to attach to, and components to trigger. Like this:

    public class GlobalStateChange
    {

        public void InvokeStateChange()
        {
            this.StateHasChanged?.Invoke(this, new EventArgs());
        }

        public event EventHandler StateHasChanged;

    }

And register this as a Singleton DI object:

   var serviceProvider = new BrowserServiceProvider(services =>
   {
         services.AddSingleton<MatBlazor.GlobalStateChange>();
   });

Then components can trigger the event like this:

mycomponent.cshtml:

@using Microsoft.AspNetCore.Blazor.Components
@inject GlobalStateChange globalState

<input type="text" bind="@Value"/>

@functions
{
    [Parameter]
    private string Value { get; set; }

    [Parameter]
    private Action<string> ValueChanged { get; set; }

    public string innerValue
    {
        get
        {
            return Value;
        }
        set
        {
            Value = value;
            ValueChanged(Value);
            globalState.InvokeStateChange();
        }
    }
 }

And the page that uses the component can attach to it like this:

    protected override void OnInit()
    {
        globalState.StateHasChanged += stateChange;
    }

    private void stateChange(object sender, EventArgs args)
    {
        StateHasChanged();
    }

And this will be the only code in the page, regardless of how many components you use.

Hope this helps.