Eseguire del codice personalizzato al click di una checkbox in Blazor

di Marco De Sanctis, in ASP.NET Core,

Parlando di Blazor, abbiamo introdotto più volte le potenzialità del databinding e mostrato come, nel caso dei controlli di input, l'attributo @bind permetta non solo di leggere il contenuto di una variabile, ma anche di aggiornarlo in automatico dopo l'interazione dell'utente. Questa tecnica è nota come databinding bidirezionale, ed elimina molte delle complessità nella gestione dello stato di pagina.

Per esempio, una checkbox può essere collegata a una proprietà di tipo bool con il semplicissimo codice in basso, e vedremo che, in maniera del tutto automatica, il contenuto del testo "MyValue is ..." si modificherà in sincrono con lo stato della checkbox stessa.

<p><label>
    <input type="checkbox" @bind="this.MyValue" /> Click me!
  </label></p>

<p>MyValue is: @MyValue</p>

@code
{
  public bool MyValue { get; set; }
}

Spesso però, ci troviamo nella necessità di eseguire del codice, magari asincrono, al click della checkbox stessa. Per esempio vorremo salvare la selezione dell'utente sul database, e purtroppo l'implementazione è meno banale di quanto sembri.

Il problema dell'evento onclick


In prima approssimazione potremmo pensare di usare l'evento @onclick:

<p>  <label>
    <input type="checkbox" @bind="this.MyValue" @onclick="ClickEventHandlerAsync" /> Click me!
  </label></p>

<p>MyValue is: @MyValue</p>

@code
{
  public bool MyValue { get; set; }

  public async Task ClickEventHandlerAsync()
  {
    await js.InvokeVoidAsync("console.log", $"MyValue was {this.MyValue}");
  }
}

Se provassimo a eseguire il codice in alto, tuttavia, ci renderemmo conto che presenta un fastidioso problema: il metodo ClickEventHandlerAsync viene infatti eseguito prima che il databinding abbia luogo, e pertanto il valore che leggiamo su MyValue è ancora quello precedente al click.

Inoltre, questa soluzione può essere fonte di pericolosi bug, perché il codice asincrono viene processato in parallelo al binding stesso. Di conseguenza, diverse linee di codice in quel metodo potrebbero leggere diversi valori di MyValue. Ce ne possiamo facilmente accorgere loggando il valore su console più di una volta: vedremo che non tutte le righe scriveranno la stessa cosa!

public async Task ClickEventHandlerAsync()
{
  await js.InvokeVoidAsync("console.log", $"1 - MyValue was {this.MyValue}");
  await js.InvokeVoidAsync("console.log", $"2 - MyValue was {this.MyValue}");
}

La soluzione più affidabile


Per risolvere il problema, dobbiamo invece sfruttare l'evento @onchange, che si scatena dopo il click, e rinunciare al binding bidirezionale. Sia @bind che @onchange, infatti, sfruttano il medesimo evento HTML, sollevando un errore di compilazione se proviamo a usarli contemporaneamente.

Riscriviamo allora il codice in pagina come segue:

<p>  <label>
    <input type="checkbox" checked="@this.MyValue"
           @onchange="this.ClickEventHandlerAsync" /> Click me!
  </label></p>

<p>MyValue is: @MyValue</p>

@code
{
  public bool MyValue { get; set; }

  public async Task ClickEventHandlerAsync(ChangeEventArgs eventArgs)
  {
    // recuperiamo il valore proveniente da HTML e lo assegniamo manualmente
    this.MyValue = (bool)eventArgs.Value;

    // a questo punto siamo sicuri che MyValue abbia il valore corretto
    await js.InvokeVoidAsync("console.log", $"MyValue was {this.MyValue}");
  }
}

Innanzi tutto la checkbox ora usa il binding in sola lettura, presentando sì il suo stato checked in base al valore di MyValue, ma non è più in grado di modificare automaticamente la variabile. Quest'ultima operazione è contenuta all'interno di ClickEventHandlerAsync, che ora accetta un parametro di tipo ChangeEventArgs che a sua volta contiene il nuovo valore per MyValue.

Si tratta sicuramente di un codice meno conciso dell'esempio precedente, ma che garantisce che il valore di MyValue sia coerente con lo stato della checkbox e non soffra dei problemi che abbiamo avuto modo di introdurre.

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Approfondimenti

I più letti di oggi