In Blazor, per presentare a video i dati presenti in un modello e salvare ciò che l'utente inserisce all'interno di un input, viene utilizzata una logica definita Two-way data binding. Questi processi permettono di mantenere una sincronia in tempo reale tra modello logico e dati rappresentati: come mostrato nell'esempio che troviamo all'interno di ogni nuova applicazione, se premiamo il bottone, nella pagina Counter, il numero mostrato viene immediatamente incrementato.
Perchè se è una funzionalità così performante, abbiamo bisogno di inserire un debounce, ossia qualcosa che dilazioni il numero di aggiornamenti? Vi sono dei contesti, anche molto comuni, in cui l'elevato numero di aggiornamenti genera del rumore non voluto, rendendo i processi della pagina lenti e volubili ad errori.
Per capire meglio utilizziamo la seconda pagina di esempio, la Weather forecast, e immaginiamo di voler aggiungere un input in cima alla tabella per filtrare i dati. La logica è molto semplice: prevede l'utilizzo di una proprietà di tipo string, che al cambiamento aggiorni una lista che sarà composta da tutti gli elementi della lista originale che hanno superato la scrematura.
<input class="form-control" @bind-value="SearchText" @bind-value:event="oninput"/> <Virtualize Items="@FilteredList" Context="weather"> @weather.Location </Virtualiza> @code{ public string SearchText = ""; IList<WeatherForecast> FilteredList { get => originalList.Where( weather => weather.Location.Contains(SearchText)).ToList(); } IList<WeatherForecast> originalList = new List<WeatherForecast>(); }
Per motivi dimostrativi abbiamo ridotto il codice mostrato, ma nel caso reale ci sarebbe stata una tabella con molte righe e colonne, che vengono create e distrutte ad ogni input dell'utente. Se inserissimo una parola come "MILANO", la logica di aggiornamento ricreerà la tabella 6 volte. Sarebbe dunque più funzionale se, prima di lanciare l'evento che aggiorni la proprietà SearchText, si attendesse del tempo, in modo da lasciare all'utente la possibilità di inserire più lettere.
Per arrivare a ciò non abbiamo proprietà o nulla di automatico, ma possiamo crearci i nostri metodi, ricordandoci che tutto quello che lega ciò che scriviamo all'interno del browser, al codice C#, sono eventi javascript, che possono essere intercettati e gestiti.
Partiamo proprio dalla scrittura di una funzione che ci permetterà di rimandare di un certo numero di secondi l'emissione dell'evento e che allo stesso tempo permetta di annullare l'evento precedente se viene richiamata una seconda volta entro il tempo prefissato. Aggiungiamo un file nella seguente posizione wwwroot/js/events.js
// funzione principale che verrà chiamata da Blazor fornendo // l'elemento html // la tipologia di evento da gestire // l'intervallo di tempo export function debounceEvent(htmlElement, eventName, delay) { registerEvent(htmlElement, eventName, delay, debounce); } function registerEvent(htmlElement, eventName, delay, filterFunction) { let raisingEvent = false; // creazione dell'evento let eventHandler = filterFunction(function (e) { raisingEvent = true; try { // se si supera il tempo pre-impostato, viene eseguito il dispatch dell'evento htmlElement.dispatchEvent(e); } finally { raisingEvent = false; } }, delay); // registrazione dell'evento sull'elemento htmlElement.addEventListener(eventName, e => { if (!raisingEvent) { e.stopImmediatePropagation(); eventHandler(e); } }); } // funzione che permette di incapsulare la funzione all'interno di un timer function debounce(func, wait) { let timer; return (...args) => { clearTimeout(timer); timer = setTimeout(() => { func.apply(this, args); }, wait); }; }
Quello che abbiamo creato pocanzi è un modulo javascript, che andrà opportunamente importato alla bisogna, utilizzando l'istanza dell'interfaccia IJSRuntime, come mostrato in seguito
public static class EventExtensions { public static async Task DebounceEvent(this IJSRuntime jsRuntime, ElementReference element, string eventName, TimeSpan delay) { await using var module = await jsRuntime.InvokeAsync<IJSObjectReference>("import", "./js/events.js"); await module.InvokeVoidAsync("debounceEvent", element, eventName, (long)delay.TotalMilliseconds); } }
Uniamo i pezzi del puzzle nella pagina Weather forecast, all'interno della quale occorrerà ottenere una referenza all'input HTML di ricerca e richiamare l'extension method per impostare la logica con cui controllare gli eventi di input
@inject IJSRuntime JSRuntime <input @ref="searchInput" class="form-control" @bind-value="SearchText" @bind-value:event="oninput"/> <Virtualize Items="@FilteredList" Context="weather"> @weather.Location </Virtualiza> @code{ ElementReference searchInput; public string SearchText = ""; IList<WeatherForecast> FilteredList { get => originalList.Where( weather => weather.Location.Contains(SearchText)).ToList(); } IList<WeatherForecast> originalList = new List<WeatherForecast>(); protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { await JSRuntime.DebounceEvent(searchInput, "input", TimeSpan.FromMilliseconds(500)); } } }
Grazie alla struttura modulare e all'extension method basato sull'interfaccia JSRuntime, possiamo applicare il debounce a qualsiasi tipologia di evento di un qualsiasi elemento, senza appesantire il codice o l'esecuzione.
Commenti
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.
Approfondimenti
Routing statico e PreRendering in una Blazor Web App
Implementare l'infinite scroll con QuickGrid in Blazor Server
Migrare una service connection a workload identity federation in Azure DevOps
Utilizzare la libreria Benchmark.NET per misurare le performance
Generare token per autenicarsi sulle API di GitHub
Code scanning e advanced security con Azure DevOps
Controllare gli accessi IP alle app con Azure Container Apps
Ottimizzare la latenza in Blazor 8 tramite InteractiveAuto render mode
C# 12: Cosa c'è di nuovo e interessante
Gestire errori funzionali tramite exception in ASP.NET Core Web API
Inizializzare i container in Azure Container Apps
Ottimizzazione dei block template in Angular 17