30

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.

ECie
  • 1,265
  • 4
  • 21
  • 50

6 Answers6

50

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...

Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
enet
  • 41,195
  • 5
  • 76
  • 113
  • 1
    Why is ValueExpression needed? – Augusto Barreto Oct 05 '20 at 21:20
  • 1
    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://stackoverflow.com/a/60659712/6152891 – enet Oct 05 '20 at 21:43
  • 1
    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`? – Ryan Buening Oct 05 '20 at 23:22
  • 1
    @Ryan Buening, I've added a "new answer' in response to your comment... – enet Oct 06 '20 at 00:25
  • 1
    Why I get this error: The type arguments for method 'TypeInference.CreateInputSelect_0(RenderTreeBuilder, int, int, Expression>, int, TValue, int, EventCallback, int, RenderFragment)' cannot be inferred from the usage. Try specifying the type arguments explicitly. – Ali Oct 29 '21 at 15:32
  • im trying this on balzor in .net 6 , im getting a compiler error Error CS0411 The type arguments for method 'TypeInference.CreateInputSelect_2(RenderTreeBuilder, int, int, object, int, Expression>, int, TValue, int, EventCallback, int, RenderFragment)' cannot be inferred from the usage. Try specifying the type arguments explicitly. – Hani Safa Aug 17 '22 at 10:46
10

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>
gericooper
  • 242
  • 2
  • 11
  • 3
    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.Value` – Jan Paolo Go Dec 20 '20 at 01:42
9

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);
    }
}
Dennis Jones
  • 115
  • 1
  • 7
1

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(); 
 } 
}
John
  • 53
  • 8
  • 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. – PajamaDuck Jun 23 '23 at 03:59
0

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

0xe1λ7r
  • 1,957
  • 22
  • 31
Hamed
  • 11
  • 1
-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; }
}
Vince G.
  • 91
  • 1
  • 8