Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Blazor OnChange Event

Tags:

html

c#

blazor

I want to bind to the value and also fire the method CalculateStandardDimensions after a change. I have tried a few different combinations of syntax but I'm still missing something to make this work. Leaving the input isn't executing the method.

How do I bind the value and call a method when the value is changed?

<input class="form-control form-control-sm" type="number" step="any" @bind-value:event="onchange=CalculateStandardDimensions()" @bind-value="Cyclone.CycloneSize" />

I have this in the code section.

    private void CalculateStandardDimensions()
    {
        // Do Stuff Here
    }
like image 995
Kris Hahn Avatar asked Jan 20 '21 04:01

Kris Hahn


Video Answer


1 Answers

It might seem like an easy task, but there are a few things you need to consider.

In Blazor, the channel from the input back to the model is handled via an event. If you are using the @bind- syntax, the compiler builds the handler for you. So, you can't (easily) update both the model value and execute another handler simultaneously.

However, there are convenient ways to achieve what you want. But they have drawbacks. Based on your context, you need to decide what is best.

Using different events

@page "/DifferentEvents"

<input class="form-control form-control-sm" type="number" step="any" 
       @bind-value:event="onchange" @oninput="CalculateStandardDimensions"  @bind-value="Cyclone.CycloneSize" />

@code
{
    public class Cyclon
    {
        public Int32 CycloneSize { get; set; } = 10;
    }

    public Cyclon Cyclone = new Cyclon();

    private void CalculateStandardDimensions(ChangeEventArgs args)
    {
        // Do Stuff Here
    }

}

In this scenario, your model will update when the input lost focus (onchange), but your method CalculateStandardDimensions is executed every time the input changes. The new value is accessible via ChangeEventArgs args.

Drawbacks: Potential inconsistency because the model doesn't know about the update, but you use it to do other stuff.

Using a mediator property with two-way binding

@page "/IntermediatePropertyWithBinding"

<input class="form-control form-control-sm" type="number" step="any" @bind-value:event="onInput" @bind-value="Size" />

@code
{
    public class Cyclon
    {
        public Int32 CycloneSize { get; set; } = 10;
    }

    public Cyclon Cyclone = new Cyclon();

    private Int32 _size;

    public Int32 Size
    {
        get => _size;
        set
        {
            _size = value;
            CalculateStandardDimensions();
            Cyclone.CycloneSize = value;
        }
    }

    private void CalculateStandardDimensions()
    {
        // Do Stuff Here
    }
}

By introducing the field _size with the Property Size, you can bind against and the setter of Size will call CalculateStandardDimensions(). Based on the event of the binding onInput or onChange you can control the time of the write back.

You have full control of the binding, and inconsistency is avoided. However, you have introduced a new field and property. This needs to be done for every property where you want to have this type of behavior.

Using a mediator property without binding

@page "/IntermediatePropertyWithoutBinding"

<input class="form-control form-control-sm" type="number" step="any" value="@_size" @oninput="ValueChanged" />

@code
{
    public class Cyclon
    {
        public Int32 CycloneSize { get; set; } = 10;
    }

    public Cyclon Cyclone = new Cyclon();

    private Int32 _size;

    private void ValueChanged(ChangeEventArgs args)
    {
        _size = Convert.ToInt32((String)args.Value);
        Cyclone.CycloneSize = _size;
        CalculateStandardDimensions();
    }

    private void CalculateStandardDimensions()
    {
        // Do Stuff Here
    }
}

We got rid of the binding and the property. The value of the input field is set directly. (without @bind-value). In the event handler, we set to update the value, write back to the model and execute the CalculateStandardDimensions() method.

We lose all the features of two-way binding but don't need a property anymore.

The EditContext OnFieldChanged event

@page "/WithEditContext"
@implements IDisposable

<EditForm EditContext="_editContext">
    <InputNumber class="form-control form-control-sm" type="number" step="any" @bind-Value:event="onchange" @bind-Value="Cyclone.CycloneSize" />
</EditForm>

@code
{
    public class Cyclon
    {
        public Int32 CycloneSize { get; set; } = 10;
    }

    private EditContext _editContext;

    public Cyclon Cyclone = new Cyclon();

    protected override void OnInitialized()
    {
        base.OnInitialized();

        _editContext = new EditContext(Cyclone);
        _editContext.OnFieldChanged += OnFormUpdated;
    }

    public void Dispose()
    {
        _editContext.OnFieldChanged -= OnFormUpdated;
    }

    private void OnFormUpdated(Object sender,  FieldChangedEventArgs args)
    {
        if(args.FieldIdentifier.FieldName == nameof(Cyclon.CycloneSize))
        {
            CalculateStandardDimensions();
        }
    }

    private void CalculateStandardDimensions()
    {
    }
}

The EditContext, which is created implicitly when using and EditForm with the Model property, has an event that is fired when a value is written back to the model. We use it by explicitly creating an EditContext and subscribing to the event. Don't forget to remove the handler later. That's why we implement IDisposable. The event handler itself checks if the field is the one we expected and executes the method CalculateStandardDimensions(). The HTML input element needs to be replaced with a InputNumber to let the binding work.

This approach has a huge amount of flexibility, but it comes with more complexity. However, you can use it with every input component like InputText or InputSelect.

like image 59
Just the benno Avatar answered Sep 28 '22 07:09

Just the benno