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
Utilizzare le collection expression in C#
Eseguire le GitHub Actions offline
Recuperare un elemento inserito nella cache del browser tramite API JavaScript
Load test di ASP.NET Core con k6
Creazione di componenti personalizzati in React.js con Tailwind CSS
Utilizzare politiche di resiliency con Azure Container App
Code scanning e advanced security con Azure DevOps
Effettuare lo stream della risposta in ASP.NET Core tramite IAsyncEnumerable
Disabilitare automaticamente un workflow di GitHub
Evitare il flickering dei componenti nel prerender di Blazor 8
Elencare le container images installate in un cluster di Kubernetes
Gestire i null nelle reactive form tipizzate di Angular
I più letti di oggi
- Utilizzare WebAssembly con .NET, ovunque
- ecco tutte le novità pubblicate sui nostri siti questa settimana: https://aspit.co/wkly buon week-end!
- Ottimizzare le performance delle collection con le classi FrozenSet e FrozenDictionary
- Utilizzare il trigger SQL con le Azure Function
- Ottimizzazione dei block template in Angular 17
- Disabilitare automaticamente un workflow di GitHub (parte 2)