blazor-two-way-binding-bind

Two-way binding en Blazor: sincronización con @bind

  • 4 min

El two-way binding es la sincronización de un valor entre el código C# y un control de la interfaz en ambas direcciones.

En el artículo anterior vimos el one-way binding, donde los datos fluían del código al HTML. Ahora necesitamos recoger también lo que el usuario escribe, marca o selecciona.

Si usamos solo lo que sabemos hasta ahora, tendríamos que hacer esto para capturar lo que escribe el usuario:

<input value="@Nombre" @onchange="@(e => Nombre = e.Value.ToString())" />
Copied!

Tener que escribir un evento lambda para cada input sería una pesadilla de mantenimiento. Por suerte, Blazor nos ofrece el Two-Way Binding (Enlace Bidireccional) mediante la directiva @bind.

La directiva @bind

Cuando usamos @bind, le estamos diciendo a Blazor: “Mantén esta variable de C# y este input HTML perfectamente sincronizados. Si uno cambia, actualiza el otro.”

<h3>Editor de Perfil</h3>

<div class="mb-3">
    <label>Nombre:</label>
    <input class="form-control" @bind="UserName" />
</div>

<div class="mb-3">
    <label>Edad:</label>
    <input type="number" class="form-control" @bind="UserAge" />
</div>

<div class="form-check">
    <input type="checkbox" class="form-check-input" @bind="IsActive" />
    <label class="form-check-label">Usuario Activo</label>
</div>

<div class="mt-3">
    <p>Hola, <strong>@UserName</strong>. Tienes @UserAge años.</p>
    <p>Estado: @(IsActive ? "🟢 Activo" : "🔴 Inactivo")</p>
</div>

@code {
    private string UserName = "Luis";
    private int UserAge = 35;
    private bool IsActive = true;
}
Copied!

Fíjate en la elegancia del sistema:

  1. No hemos tenido que convertir string a int manualmente para la edad. Blazor lo hace por nosotros.
  2. Si UserAge cambia en el código, el input se actualiza.
  3. Si el usuario cambia el input, UserAge se actualiza.

Qué genera @bind

@bind es azúcar sintáctico alrededor del valor del control y su evento de cambio.

Cuando el compilador de Razor ve esto:

<input @bind="Texto" />
Copied!

Conceptualmente, genera algo parecido a esto (el código real incluye conversión y gestión de errores):

<input value="@Texto" @onchange="@((ChangeEventArgs __e) => Texto = __e.Value.ToString())" />
Copied!

Entender esto es importante para cuando necesitemos personalizar el comportamiento, como veremos ahora mismo.

Controlando el momento de actualización (@bind:event)

Por defecto, @bind actualiza la variable cuando el input pierde el foco (evento onchange).

Pruébalo: escribe en un input vinculado. Verás que el texto de abajo (<p>Hola @UserName</p>) no cambia letra a letra, sino solo cuando haces clic fuera o pulsas Tab.

¿Y si queremos una búsqueda en tiempo real? ¿O validación instantánea? Necesitamos cambiar el evento disparador a oninput. Para eso usamos el modificador @bind.

<h3>Buscador en tiempo real</h3>

<input class="form-control"
       @bind="SearchTerm"
       @bind:event="oninput"
       placeholder="Escribe para buscar..." />

<p>Buscando: @SearchTerm</p>

@code {
    private string SearchTerm { get; set; } = string.Empty;
}
Copied!

Ahora, la variable SearchTerm se actualiza con cada pulsación de tecla (keystroke).

Ojo con el rendimiento: Usar oninput provoca una actualización con cada cambio. Si además lanzas búsquedas o trabajo costoso, aplica debounce o cancelación para no iniciar una operación por cada tecla.

Formateo de fechas con @bind:format

Trabajar con fechas en inputs (<input type="date">) suele ser un dolor de cabeza porque el navegador espera un formato específico (yyyy-MM-dd), independientemente de la cultura de tu usuario.

Blazor nos ayuda con el modificador @bind.

<label>Fecha de Nacimiento:</label>

<input type="date"
       @bind="BirthDate"
       @bind:format="yyyy-MM-dd" />

<p>Naciste el: @BirthDate.ToShortDateString()</p>

@code {
    private DateTime BirthDate = DateTime.Today;
}
Copied!

Al especificar el formato, Blazor se encarga de parsear la entrada del navegador y convertirla correctamente a tu objeto DateTime, y viceversa.

Listas desplegables

El binding funciona perfectamente con elementos <select>, tanto para selecciones simples como múltiples.

<select class="form-select" @bind="SelectedCity">
    <option value="">-- Selecciona ciudad --</option>
    @foreach (var city in Cities)
    {
        <option value="@city.Code">@city.Name</option>
    }
</select>

<p>Has seleccionado el código: @SelectedCity</p>

@code {
    private string SelectedCity = string.Empty;

    // Clase simple City { Code, Name }
    private List<City> Cities = [];
}
Copied!

Blazor es lo suficientemente listo para seleccionar el <option> correcto al cargar la página basándose en el valor inicial de SelectedCity.

Binding en componentes personalizados

Hasta ahora hemos hablado de elementos HTML. Pero, ¿podemos hacer Two-Way Binding entre dos componentes de Blazor? ¿Puede un padre “bindearse” a un dato de un hijo?

Sí, pero requiere seguir una convención de nombres estricta.

Si el hijo tiene un parámetro Value, debe tener un evento llamado ValueChanged.

Hijo (InputPersonalizado.razor):

<input value="@Value" @oninput="OnInputChanged" />

@code {
    [Parameter] public string Value { get; set; } = string.Empty;

    // Convención: NombreDelParametro + Changed
    [Parameter] public EventCallback<string> ValueChanged { get; set; }

    private async Task OnInputChanged(ChangeEventArgs e)
    {
        // Avisamos al padre del cambio
        await ValueChanged.InvokeAsync(e.Value.ToString());
    }
}
Copied!

Padre:

<InputPersonalizado @bind-Value="MiVariable" />
Copied!