When we write applications, we want to know when a user changes the value in input fields and what the new value is. We get this information through change events available for HTML input-type elements. We can define an onchange event attribute on the input element to handle what happens when the event fires.
In this article, we will demonstrate how to use onchange event with the select dropdown element in Blazor WebAssembly.
Let’s start
@onchange vs @bind in Blazor
Our application is a simple burger configurator where we choose the topping from a dropdown menu. We will create a new Blazor WebAssembly application with Visual Studio Project Wizard or use the terminal dotnet new blazorwasm
command.
In the code section of our index.razor
file we add:
@code { public record Topping(int Id, string Name); public string? selectedTopping; public List<Topping> toppings = new List<Topping>() { new Topping(1, "No Topping"), new Topping(2, "Cheese"), new Topping(3, "Onions"), new Topping(4, "Pickles"), new Topping(5, "Avocado"), new Topping(6, "Lettuce") }; }
We create a Topping
record
type and initialize several Topping
records with Id
and Name
. We also declare a string variable selectedTopping
.
In the body of the index.razor
, we add:
@page "/" <PageTitle>Burger Configurator</PageTitle> <h1>Burger Configurator</h1> <p>Free topping: @selectedTopping</p> <p>Please choose your topping from the list below:</p> <span>Free topping:</span> <select class="select-element" @bind="selectedTopping"> @foreach (var topping in toppings) { <option value="@topping.Name">@topping.Name</option> } </select>
To use onchange
event with select
dropdown, we add a <select>
element with a foreach
loop that goes through our toppings and displays it as a dropdown in our application UI. Notice that in the <select>
element, we use @bind
to bind the selected value in the dropdown with our variable @selectedTopping
.
When we run the application and select a topping, the topping appears in the line:
<p>Free topping: @selectedTopping</p>
It seems that there is an @onchange
event present even though we didn’t declare it in our <select>
element.
It happens because @bind
has @onchange
event registered which handles changes, so our line:
<select class="select-element" @bind="selectedTopping">
is equal to:
<select class="select-element" value="@selectedTopping" @onchange="@((ChangeEventArgs e) => selectedTopping = e.Value.ToString())">
Our <select>
element has an @onchange
event attribute with lambda expression where it assigns the selected value to the @selectedTopping
variable after the change event fires.
The @bind
is a good choice for works storing the value in the variable after the change, but what if we need to perform additional computations based on the selected value?
Let’s see how we use the @onchange
event to calculate the cost of a burger based on the topping we select.
How Do We Use @onchange Without @bind?
Firstly, let’s change our Topping
record:
@code { public record Topping(int Id, string Name, double Price); public string? selectedTopping; public string? selectedSecondTopping; public List<Topping> toppings = new List<Topping>() { new Topping(1, "No Topping", 0), new Topping(2, "Cheese", 2.4), new Topping(3, "Onions", 0.7), new Topping(4, "Pickles", 1.3), new Topping(5, "Avocado", 4.6), new Topping(6, "Lettuce", 1.1) }; }
In the Topping
record declaration, we add a double
type Price
and add prices for each topping when we initialize the toppings
list, we also add a selectedSecondTopping
string variable.
We add a Price
preceded by +
and $
signs in our foreach
loop. This way, we can see the $ amount each topping adds when we select it:
@foreach (var topping in toppings) { <option value="@topping.Name">@topping.Name [email protected]</option> }
Let’s add a method we will use in our @onchange
event:
@code { public record Topping(int Id, string Name, double Price); public string? selectedTopping; public string? selectedSecondTopping; public static double baseBurgerCost = 5.4; public double totalCost = baseBurgerCost; public List<Topping> toppings = new List<Topping>() { new Topping(1, "No Topping", 0), new Topping(2, "Cheese", 2.4), new Topping(3, "Onions", 0.7), new Topping(4, "Pickles", 1.3), new Topping(5, "Avocado", 4.6), new Topping(6, "Lettuce", 1.1) }; public void HandleChange(ChangeEventArgs args) { @foreach(var topping in toppings) { if(topping.Id == int.Parse(args.Value.ToString())) { selectedSecondTopping = topping.Name; totalCost = Math.Round(baseBurgerCost + topping.Price, 2); } } } }
First, we add a baseBurgerCost
variable which is a cost without a topping, and a totalCost
variable initialized with the baseBurgerCost
value.
Then, we create a HandleChange()
method that executes when the @onchange
event fires. Inside, we have a foreach
loop that goes through toppings
and checks whether the id
of the currently selected topping equals a topping from the List
. When it finds a match, it assigns a name to the selectedSecondTopping
variable, calculates the total cost of the burger, and sets it to the totalCost
variable.
We also need to adapt our page body:
@page "/" <PageTitle>Burger Configurator</PageTitle> <h1>Burger Configurator</h1> <p>Burger without topping: $@baseBurgerCost</p> <p>Free topping: @selectedTopping</p> <p>Total Cost: $@totalCost</p> <p>Please choose your toppings from the list below:</p> <span>Free topping:</span> <select class="select-element" @bind="selectedTopping"> @foreach (var topping in toppings) { <option value="@topping.Name">@topping.Name</option> } </select> <span>Second topping:</span> <select class="select-element" @onchange="@HandleChange"> @foreach (var topping in toppings) { <option value="@topping.Id">@topping.Name [email protected]</option> } </select>
We add baseBurgerCost
and totalCost
variable outputs to see the results.
Then, we set the topping id
as a value of the <option>
element and assign the HandleChange()
method to the @onchange
attribute.
Our entire logic for total cost calculation is now inside the HandleChange()
method which executes when the @onchange
event fires.
Can We Use @bind With @onchange?
Let’s add @bind
to our <select>
element:
<select class="select-element" @bind="@selectedSecondTopping" @onchange="@HandleChange"> @foreach (var topping in toppings) { <option value="@topping.Id">@topping.Name [email protected]</option> } </select>
The compiler will immediately complain and show us an error on the @onchange
attribute. The reason is that @bind
uses @onchange
internally, so it is not possible to use @bind
and @onchange
on the same element, as it throws an error:
The attribute 'onchange' is used two or more times for this element. Attributes must be unique (case-insensitive). The attribute 'onchange' is used by the '@bind' directive attribute.
@bind:after for Asynchronous Logic in .NET 7+
From .NET 7, Microsoft recommends using @bind:after
for asynchronous logic. The assigned method won’t execute until the synchronous bind for the value is complete. The method we pass to @bind:after
needs to return either Task
or Action
. In this scenario, the <select>
element has a @bind
attribute that binds the value of the selected element. Then, the @bind:after
is executed asynchronously after @bind
is bound to the selected element’s value.
Let’s see a @bind:after
example with a second topping:
@page "/" <PageTitle>Burger Configurator</PageTitle> <h1>Burger Configurator</h1> <p>Burger without topping: $@baseBurgerCost</p> <p>Free topping: @selectedTopping</p> <p>Total Cost: $@totalCost</p> <p>Grand Total: $@grandTotal</p> <p>Please choose your toppings from the list below:</p> <span>Free topping:</span> <select class="select-element" @bind="selectedTopping"> @foreach (var topping in toppings) { <option value="@topping.Name">@topping.Name</option> } </select> <span>Second topping:</span> <select class="select-element" @onchange="@HandleChange"> @foreach (var topping in toppings) { <option value="@topping.Id">@topping.Name [email protected]</option> } </select> <span>Third topping:</span> <select class="select-element" @bind="selectedThirdTopping" @bind:after="CalculateGrandTotal"> @foreach (var topping in toppings) { <option value="@topping.Id">@topping.Name [email protected]</option> } </select>
We add another <select>
element along with a @grandTotal
output.
Next, we add the following variables and a method to our @code
section:
public string? selectedThirdTopping; public double grandTotal; public async Task CalculateGrandTotal() { await Task.Delay(2000); @foreach (var topping in toppings) { if (topping.Id == int.Parse(selectedThirdTopping)) { grandTotal = Math.Round(totalCost + topping.Price, 2); } } }
With Task.Delay()
we simulate an asynchronous request that will fetch data and return a result after a certain amount of time. The important thing to note here is that our asynchronous logic will execute only after the value has been bound to the variable set to the @bind
attribute. This way we can use the selected value to make an asynchronous request and return data.
Conclusion
User input is a core part of any user-facing application. We need to get a currently selected value and be able to apply additional logic after the change occurs.
We explored the use of onchange event with the select dropdown in a Blazor application by using the @bind to assign the current value to the variable as it already internally contains an onchange event.
When we have our logic in a separate method, we can use the onchange attribute to execute our code when the change occurs. We can use the @bind:after attribute for asynchronous methods, as it will run after the new value is bound to the assigned variable.