onChange event not firing Blazor InputSelect
Asked Answered
S

8

39

I have the following Code for an InputSelect

               <InputSelect class="form-control form-control form-control-sm"
                             placeholder="Role"
                             disabled="@IsReadOnly"
                             @bind-Value="Model.Role"
                              @onchange="@RoleChanged">
                    <option value="Member">Member</option>
                    <option value="Admin">Admin</option>
                    <option value="Pioner">Pioneer</option>
                    <option value="Retailer">Retailer</option>
                </InputSelect>

And for the Code:

bool ShowCreated;
bool ShowUpdated;
bool IsReadOnly;
string SelectedRole;

public EditForm AccountForm;
public Multi.Dtos.RegistrationDto Model = new Dtos.RegistrationDto() { Role = "Member" };

public void RoleChanged(ChangeEventArgs e)
{
    SelectedRole = e.Value.ToString();
    Console.WriteLine(e.Value);
}

The RoleChange function is not invoked when i try to select a new item. Can someone point out what wrong with this. The value of the proper has changed but the Event was not called.

Stanstance answered 12/5, 2020 at 15:54 Comment(3)
You cannot do bind-value and @onchange same time.Thousandth
I found that out during debugging. I wonder what would be the recommended approach on this.?Stanstance
You can implement IPropertyChange on your model and bind some action to events.Thousandth
I
56

Note:

  • InputSelect is a component element, not HTML element, which is why you cannot apply to it the @onchange compiler directive. This directive is applied to elements, usually but not necessarily with the value attribute to form the so- called two way data-binding.

  • @bind-Value is a compiler directive directive instructing the compiler to produce code that enable two-way data binding between components. Applying @bind-Value to the InputSelect component requires you (already done in this case by the Blazor team) to define a parameter property named Value and an EventCallback 'delegate', conventionally named ValueChanged. Value and ValueChanged are properties of the InputSelect component.

There are a couple of ways to do this:

 <InputSelect ValueExpression="@(()=>comment.Country)" 
              Value="@comment.Country" 
              ValueChanged="@((string value) => OnValueChanged(value ))">
        <option value="">Select country...</option>
        <option value="USA">USA</option>
        <option value="Britain">Britain</option>
        <option value="Germany">Germany</option>
        <option value="Israel">Israel</option>
 </InputSelect>

And

private Task OnValueChanged(string value)
{
    // Assign the selected value to the Model 
    comment.Country = value;
    return Task.CompletedTask;
} 

You can also implement INotifyPropertyChanged.PropertyChanged Event in your Model, as dani herrera has suggested. You do that if you want to do away with the Forms components, including the EditForm, and use the normal HTML tags instead, in which case you'll be able to do something like:

<input type="text" value="@MyValue" @onchange=OnChange"/>

Of course your model class is going to be thick, and it should communicate with the EditContext....

Hope this helps...

Isacco answered 12/5, 2020 at 23:8 Comment(7)
Why is ValueExpression needed?Sappanwood
Usually, you don't have to provide ValueExpression...The compiler can do the two-way binding without it. But sometimes you need to tell the compiler how to do it...You can read more about it in my answer here: https://mcmap.net/q/247512/-when-to-use-valuechanged-and-valueexpression-in-blazorIsacco
I'm coming from working in Angular and the fact that in Blazor you can use @onchange for InputText, but can't for InputSelect is super confusing and convoluted. Is there a way to create a CustomInputSelect.razor component to abstract this change logic out so it isn't needed on each InputSelect?Elohim
@Ryan Buening, I've added a "new answer' in response to your comment...Isacco
Why I get this error: The type arguments for method 'TypeInference.CreateInputSelect_0<TValue>(RenderTreeBuilder, int, int, Expression<Func<TValue>>, int, TValue, int, EventCallback<TValue>, int, RenderFragment)' cannot be inferred from the usage. Try specifying the type arguments explicitly.Huggermugger
im trying this on balzor in .net 6 , im getting a compiler error Error CS0411 The type arguments for method 'TypeInference.CreateInputSelect_2<TValue>(RenderTreeBuilder, int, int, object, int, Expression<Func<TValue>>, int, TValue, int, EventCallback<TValue>, int, RenderFragment)' cannot be inferred from the usage. Try specifying the type arguments explicitly.Caspar
That's fine except that I want a combo-box. If there is a large list the user needs to be able to type in it to filter it. Has the Blazor team thought of that? (Windows has had combo-box since last century)Croquet
S
15

The problem is the @bind attribute makes use of the @onchange event and Blazor will not allow multiple @onchange event handlers. Workarounds in the code below:

  • Method 1: This is the vanilla example. It shows how to wire up a dropdown using an HTML select tag when you do not require an onchange event handler.
  • Method 2: This is, I think, the simplest if you need 2-way data binding AND an event handler. It uses the HTML select tag (not a Blazor component) with 1-way data binding using the "value" attribute. Since the @bind attribute is not used, we are free to attach a handler to the @onchange event. This handler, as well as handling the event, also needs to populate the property in order to achieve 2-way data binding.
  • Method 3: If you are using the whole Blazor EditForm and InputText/InputSelect/etc components infrastructure, this method may be best for you. Without the EditContext, the example shows 2-way binding using @bind-Value. In order to handle the onchange event for any component, we add an event handler (EditContext.OnFieldChanged) for the entire form. The handler checks to see which property was changed (based on the FieldName) and fires the required event handler accordingly.

I created this page to remind me of the options for this. Hope it helps anyone who finds their way here.

@page "/dropdown"

<h2 class='@(hightlight ? "bg-info" : "")'>
    User Id: @UserId
</h2>

<hr />

<h3>Using Select Tag</h3>
<p>
    <label>
        Method 1 (2-way binding but do not need to handle an onchange event):<br />
        <select @bind="UserId">
            <option value=""></option>
            @foreach (var u in users)
            {
                <option value="@u.Key">@u.Value</option>
            }
        </select>
    </label>
</p>
<p>
    <label>
        Method 2 (need to handle the onchange event; 2-way binding via the control onchange event handler):<br />
        <select value="@UserId" @onchange="OnChangeUserId">
            <option value=""></option>
            @foreach (var u in users)
            {
                <option value="@u.Key">@u.Value</option>
            }
        </select>
    </label>
</p>

<h3>Using InputSelect Tag</h3>
<EditForm EditContext="EditContext">
    <p>
        <label>
            Method 3 (2-way binding;  event handling via the EditForm EditContext.OnFieldChanged)<br />
            <InputSelect @bind-Value="@UserId">
                <option value=""></option>
                @foreach (var u in users)
                {
                    <option value="@u.Key">@u.Value</option>
                }
            </InputSelect>
        </label>
    </p>
</EditForm>

@code {
    private EditContext EditContext;
    private Dictionary<int, string> users = new Dictionary<int, string>();

    private int? UserId { get; set; }
    private bool hightlight { get; set; }

    protected override void OnInitialized()
    {
        EditContext = new EditContext(users);
        EditContext.OnFieldChanged += EditContext_OnFieldChanged;
    }

    protected override void OnParametersSet()
    {
        // simulate getting current data from the db (which would use async version of this event handler)
        users.Clear();
        users.Add(1, "Bob");
        users.Add(2, "Sue");
        users.Add(3, "R2D2");
        UserId = 2;

        base.OnParametersSet();
    }

    private void EditContext_OnFieldChanged(object sender, FieldChangedEventArgs e)
    {
        if (e.FieldIdentifier.FieldName == "UserId")
        {
            DoStuff();
        }
    }

    private void OnChangeUserId(ChangeEventArgs args)
    {
        // select tag's "value" attribute provide one-way data binding; to get 2-way,
        // we need to manually set the value of the UserId based on the ChangeEventArgs
        if (args.Value == null)
            UserId = null;
        else
            UserId = int.Parse(args.Value.ToString());

        DoStuff();
    }

    private void DoStuff()
    {
        hightlight = (UserId == 3);
    }
}
Samarasamarang answered 5/5, 2021 at 20:12 Comment(5)
Some textual introduction and description might be in order to make this helpful to othersPurlin
@Purlin Done. Thanks!Samarasamarang
Unsed the Method 2, unfortunately we can't have two-way binding AND change event firing with InputSelect directly ...Inscrutable
Yeah, Method 2 is a 1-way binding - control won't change when model changesRode
Actually, they all do 2-way databinding. Did you try the sample code?Samarasamarang
E
12

Use @bind-Value:after, as mentioned in the official microsoft resources,

For your example:

               <InputSelect class="form-control form-control form-control-sm"
                         placeholder="Role"
                         disabled="@IsReadOnly"
                         @bind-Value="Model.Role"
                         @bind-Value:after="RoleChanged">
                <option value="Member">Member</option>
                <option value="Admin">Admin</option>
                <option value="Pioner">Pioneer</option>
                <option value="Retailer">Retailer</option>
            </InputSelect>

Code:

bool ShowCreated;
bool ShowUpdated;
bool IsReadOnly;
string SelectedRole;

public EditForm AccountForm;
public Multi.Dtos.RegistrationDto Model = new Dtos.RegistrationDto() { Role = "Member" };

public void RoleChanged()
{
    Console.WriteLine(Model.Role);
}
Emelinaemeline answered 3/11, 2023 at 13:55 Comment(4)
Thank you for your interest in contributing to the Stack Overflow community. This question already has quite a few answers—including one that has been extensively validated by the community. Are you certain your approach hasn’t been given previously? If so, it would be useful to explain how your approach is different, under what circumstances your approach might be preferred, and/or why you think the previous answers aren’t sufficient. Can you kindly edit your answer to offer an explanation?Convex
Thank you for this. The web is filled with answers suggesting using Value + ValueExpression + ValueChanged, but your answer is the one that is currently recommended by Microsoft and is specifically designed for the usecase of OP. Blazor is evolving :)Remanent
+1, can also confirm this is exactly what I was looking for without using Value + ValueExpression + ValueChanged. Thanks very much for your help!Gastelum
This is the way. Works in Blazor apps .net 6 and .net 8.Surpass
F
11

The request is not that old, so you may consider the following which works with EditForm (basically hook up onto another event) => at oninput eventhandler you pass The ChangeEventArgs var, and so you can get the value to process

<InputSelect @bind-Value="@labBook.StockID" @oninput="OnLabbookStockChange" class="form-control">
                            @if (lstStocks != null)
                                {
                                <option value="">select...</option>
                                @foreach (var stock in lstStocks)
                                    {
                                    <option value="@stock.StockID">@stock.TestArticle.TAName</option>
                                    }
                                }
                        </InputSelect>
Finery answered 7/10, 2020 at 10:22 Comment(1)
note that @oninput will fire before the bound model/property is updated with the selected value. You can grab the selected value in the ChangedEventArgs.ValuePadnag
G
2

May be late to the party, but I found the easiest thing to do is use the @bind-Value="MyProperty" and then for the property call the function in the setter. Therefore, no need to use @onchanged. so in the c# model:

private T _myProperty;

public T MyProperty 
{
 get => _myProperty; 
 set => 
 { 
   _myProperty = value;
   MyFunction(); 
 } 
}
Glenine answered 17/10, 2022 at 4:10 Comment(1)
Just a small point, the compiler complained about arrow functions not being allowed in the setter when combined with the braces. But otherwise an excellent suggestion saved me a lot of time trying to create a work-around.Callan
F
0

Instead of using the onchange event. You may want to attempt using oninput event instead.

Finkelstein answered 1/8, 2022 at 15:23 Comment(0)
T
0

if its a component also in net 8. add this code after @page directive

@rendermode RenderMode.InteractiveServer
Tumescent answered 14/4 at 14:32 Comment(0)
S
-5

I solved this simply using the @onclick event. It fires multiple times, but the last time it fires, its the selected item.

<div>
    <EditForm Model="model">
        <InputSelect @bind-Value="model.SimulatorType" @onclick="SelectionChanged">
            @foreach (var value in Enum.GetValues(typeof(SimulatorType)))
            {
                <option>@value</option>
            }
        </InputSelect>
    </EditForm>
</div>

@code {
    class Model
    {
        public SimulatorType SimulatorType
        {
            get;set;
        }
    }

    Model model = new Model();

    private async void SelectionChanged()
    {
        await OnSelectionChanged.InvokeAsync(model.SimulatorType.ToString());
    }

    [Parameter]
    public EventCallback<string> OnSelectionChanged { get; set; }
}
Suzan answered 12/2, 2021 at 0:57 Comment(1)
Good lord please don't do thatRat

© 2022 - 2024 — McMap. All rights reserved.