30

So I am building a Blazor component where I want to type into an input and fire an AJAX request to get filtered data from the server. I tried this

<input type="text" @bind="NameFilter" @onchange="FilterChangedAsync" />

However this results in 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.

I thought about calling the method in the NameFilter property setter but in this case I can't await it. What is the proper way to achieve the desired behavior?

2
  • stackoverflow.com/a/58135444/1308743 Commented Jan 14, 2020 at 17:29
  • 4
    @Kyle the problem with that is that you can't await async methods in a property setter Commented Jan 14, 2020 at 18:48

4 Answers 4

41

The @bind attribute is a compiler directive attribute instructing the compiler to create code that enables two way data binding, from a variable to the element, and from the element to the variable. Behind the scene, the compiler creates the onchange event handler whose role is to update the variable when the change event is triggered. Thus, you can't use the onchange twice. Instead you should do the following:

<input type="text" @bind="NameFilter" />

To retrieve the data entered define a property like this:

public string NameFilter { get; set; } 

In that case you can add a button control with a click event handler that can access the value of NameFilter, and use it for your filtering calls, like this:

<button class="btn btn-primary" @onclick="@FilterMe">Filter Me</button>

And,

private void FilterMe()
    {
        var filter = NameFilter;
    }

Or still better, bind the NameFilter variable to the value attribute, plus defining an event handler, like this:

<input type="text" value="@NameFilter" @onchange="FilterChangedAsync" />

But in that case it is your responsibility to update the bound variable, which you can do in the event handler itself, or using a lambada expression as the value of @onchange

 private void FilterChangedAsync(ChangeEventArgs args)
    {
        NameFilter = args.Value.ToString();
    }

This is how you update the NameFilter property with lambada expression:

<input type="text" value="@NameFilter" @onchange="@(( args ) => NameFilter = args.Value.ToString())" />

Note: The change event is triggered only when you tab out of the text box control, and this behavior may not suit your filtering requirements. The input event, on the other hand, occurs each time you type on the keyboard.

Using the input event:

<input type="text" @bind-value="@NameFilter" @bind-value:event="oninput" />

Or you can do it with an accompanying method like this:

<input type="text" value="@NameFilter" @oninput="@FilterChangedAsync" />

and

 private void FilterChangedAsync(ChangeEventArgs args)
    {
        NameFilter = args.Value.ToString();
    }

Good luck...

Sign up to request clarification or add additional context in comments.

4 Comments

So how do I get the value into the NameFilter property then? Do I have to reference the element with @ref? BTW you are right about onchange firing only when losing focus. For some reason I thought it also fired when enter is pressed...
You can @Bind the value AND bind an event on keyup. Might not be quite what you want, but those two events will work hand in hand.
Thanks for thoroughly explanation especially regarding the @oninput
Did not work for me. I find that the property is still null upon submitting my form, even though I used @bind.
20

>= Net7

Quoting Blazor data binding get/set/after modifiers

In .NET 7 you can now easily run async logic after a binding event has completed using the new @bind:after modifier:

<input @bind="searchText" @bind:after="PerformSearch" />

@code {
    string searchText;

    async Task PerformSearch()
    {
        // ... do something asynchronously with 'searchText' ...
    }
}

also useful:

<input @bind:get="Value" @bind:set="ValueChanged" />

@code {
    [Parameter] public TValue Value { get; set; }
    [Parameter] public EventCallback<TValue> ValueChanged { get; set; }
}

2 Comments

when bind:after is fired? How to define if I want it onChange or onInput, or keyDown?
Thanks! Moving everything to .Net7 ! Been waiting for this for a while - way too difficult in earlier versions to get common things done.
1

As a complement of Enet's answer, you can also declare async onchange handlers like this @onchange=@(async (ChangeEventArgs e) => await { /* Do your thing */ OnItemChanged(args: e); } )

Also to complete Dani's answer, here a list of directives you can use to get a fine grained control over your bindings:

Directive Usage
@bind= Standard two-way binding
@bind:get= Custom getter
@bind:set= Custom setter
@bind:event= Custom event for binding updates
@bind:after= Runs logic after value updates
@bind-{attribute} Binds to a specific attribute
@bind-{attribute}:event= Custom event for a specific attribute

For example : <input type="text" @bind="Name" @bind:event="oninput" />

The :event directive let's you define another event handler than the default onchange which lets you handle updates as the users types.

2 Comments

For some reason, my table won't format correctly :/
Markdown needs a newline every now and then
0

Since this is the number one answer that did not help me, I'll post the actual solution here, as the first one is named async but defacto is not.

It is important to return a Task from the async method and not void, that is the whole trick.

Code example:

<InputFile accept=".xlsx" OnChange="@LoadFile" single />
...
@code {
private ExcelData? data;
private async Task LoadFile(InputFileChangeEventArgs e)
{
    data = await ExcelReader.ImportExcelFile(e.File);
}

}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.