Binding decimal numbers using the oninput event in Blazor

 
 
  • Gérald Barré

I needed to bind an <input type=number> in a Blazor application and get the value as soon as the user changes it. This works fine for integer values, but decimal values are more problematic.

#First attempt: <input type=number>

First, I started with a basic solution. I used an input element and set the oninput event to bind the value as soon as the user changes it.

Razor
<input type="number" @bind-value="model.Value" @bind-value:event="oninput" />
<p>Value: @model.ValueSingle</p>

@code {
    Model model = new Model();

    public class Model
    {
        public float Value { get; set; }
    }
}

This code works reasonably well, but there are edge cases that break it. For example, you cannot enter a value such as "0.01". As soon as you type "0.0", the value is parsed (0f) and assigned to model.Value. Blazor then re-renders the component and sets the input value to 0f.ToString(), which is "0". As a result, "0.0" is immediately reset to "0". You can see this behavior in the following video:

⇒ This solution doesn't work for my needs

#Second attempt: InputNumber

For my second attempt, I looked at the InputNumber<T> component:

Razor
<EditForm Model="model">
    <InputNumber @bind-Value="model.Value" />
    <p>Value: @model.Value</p>
</EditForm>

@code {
    Model model = new Model();

    public class Model
    {
        public float Value { get; set; }
    }
}

The InputNumber<T> component handles any decimal value without issue. However, there is no way to customize the binding event. It only uses the onchange event.

⇒ This solution doesn't work for my needs

#Third attempt: Custom component

Since the default binding behavior does not meet my needs, I created a new component that inherits from InputNumber and implements custom binding logic:

Razor
@* file: InputNumberOnInput.razor *@
@typeparam T
@inherits InputNumber<T>

<input @attributes="AdditionalAttributes"
       type="number"
       class="@CssClass"
       value="@stringValue"
       @oninput="OnInput"
       @onblur="OnBlur" />

@code {
    private string stringValue;
    private T lastParsedValue;

    protected override void OnParametersSet()
    {
        // Only overwrite the "stringValue" when the Value is different
        if (!Equals(CurrentValue, lastParsedValue))
        {
            lastParsedValue = CurrentValue;
            stringValue = CurrentValueAsString;
        }
    }

    private void OnInput(ChangeEventArgs e)
    {
        // Update the value
        CurrentValueAsString = stringValue = (string)e.Value;
        lastParsedValue = CurrentValue;
    }

    private void OnBlur(FocusEventArgs e)
    {
        // Overwrite the stringValue property with the parsed value.
        // This call Value.ToString(), so the value in the input is well formatted.
        // note: Ensure the string value is valid before updating the content
        if (!EditContext.GetValidationMessages(FieldIdentifier).Any())
        {
            stringValue = CurrentValueAsString;
        }
    }
}

You can use this component like the InputNumber component:

Razor
<EditForm Model="model">
    <InputNumberOnInput @bind-Value="model.Value" />
    <p>Value: @model.Value</p>
</EditForm>

@code {
    Model model = new Model();

    public class Model
    {
        public float Value { get; set; }
    }
}

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?