How can I add a delay to an event (OnInput) in Blazor ?
For example, if a user is typing in the text field and you want to wait until the user has finished typing.
Blazor.Templates::3.0.0-preview8.19405.7
Code:
@page "/"
<input type="text" @bind="Data" @oninput="OnInputHandler"/>
<p>@Data</p>
@code {
public string Data { get; set; }
public void OnInputHandler(UIChangeEventArgs e)
{
Data = e.Value.ToString();
}
}
There is no single solution to your question. The following code is just one approach. Take a look and adapt it to your requirements. The code resets a timer on each keyup
, only last timer raises the OnUserFinish
event.
Remember to dispose timer by implementing IDisposable
@using System.Timers;
@implements IDisposable;
<input type="text" @bind="Data" @bind:event="oninput"
@onkeyup="@ResetTimer"/>
<p >UI Data: @Data
<br>Backend Data: @DataFromBackend</p>
@code {
public string Data { get; set; } = string.Empty;
public string DataFromBackend { get; set; } = string.Empty;
private Timer aTimer = default!;
protected override void OnInitialized()
{
aTimer = new Timer(1000);
aTimer.Elapsed += OnUserFinish;
aTimer.AutoReset = false;
}
void ResetTimer(KeyboardEventArgs e)
{
aTimer.Stop();
aTimer.Start();
}
private async void OnUserFinish(Object? source, ElapsedEventArgs e)
{
// https://stackoverflow.com/a/19415703/842935
// Call backend
DataFromBackend = await Task.FromResult( Data + " from backend");
await InvokeAsync( StateHasChanged );
}
void IDisposable.Dispose()
=>
aTimer?.Dispose();
}
One example of use case of this code is avoiding backend requests, because the request is not sent until user stops typing.
This answer is the middle ground between the previous answers, i.e. between DIY and using a full-blown reactive UI framework.
It utilizes the powerful Reactive.Extensions library (a.k.a. Rx), which in my opinion is the only reasonable way to solve such problems in normal scenarios.
After installing the NuGet package System.Reactive
you can import the needed namespaces in your component:
@using System.Reactive.Subjects
@using System.Reactive.Linq
Create a Subject
field on your component that will act as the glue between the input event and your Observable
pipeline:
@code {
private Subject<ChangeEventArgs> searchTerm = new();
// ...
}
Connect the Subject
with your input
:
<input type="text" class="form-control" @[email protected]>
Finally, define the Observable
pipeline:
@code {
// ...
private Thing[]? things;
protected override async Task OnInitializedAsync() {
searchTerm
.Throttle(TimeSpan.FromMilliseconds(200))
.Select(e => (string?)e.Value)
.Select(v => v?.Trim())
.DistinctUntilChanged()
.SelectMany(SearchThings)
.Subscribe(ts => {
things = ts;
StateHasChanged();
});
}
private Task<Thing[]> SearchThings(string? searchTerm = null)
=> HttpClient.GetFromJsonAsync<Thing[]>($"api/things?search={searchTerm}")
}
The example pipeline above will...
ChangeEventArgs
,things
,If you have something like the below in your markup, you will see it being updated when you type:
@foreach (var thing in things) {
<ThingDisplay Item=@thing @[email protected] />
}
You should properly dispose the event subscription like so:
@implements IDisposable // top of your component
// markup
@code {
// ...
private IDisposable? subscription;
public void Dispose() => subscription?.Dispose();
protected override async Task OnInitializedAsync() {
subscription = searchTerm
.Throttle(TimeSpan.FromMilliseconds(200))
// ...
.Subscribe(/* ... */);
}
}
Subscribe()
actually returns an IDisposable
that you should store and dispose along with your component. But do not use using
on it, because this would destroy the subscription prematurely.
There are some things I haven't figured out yet:
StateHasChanged()
?Subscribe()
and bind directly to the Observable
inside the markup like you would do in Angular using the async
pipe?Subject
? Rx supports creating Observable
s from C# Event
s, but how do I get the C# object for the oninput
event?If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With