Quando eu tenho um modelo no EntityFrameworkCore que tem os atributos int CountryId e Country Country, e no EditForm eu coloco um @onchange no InputSelect nenhum evento é gerado. Mas, se eu colocar um @oninput o evento funciona. Qual o motivo?
Esse é um comportamento esperado e se deve à forma como o Blazor lida com a sincronização de dados e os eventos dos elementos InputSelect.
O Motivo
O evento @onchange em um componente InputSelect do Blazor é projetado para ser disparado quando o valor do elemento é alterado e o controle perde o foco (um evento de "blur"). Isso é o que tipicamente acontece quando um usuário seleciona uma opção e clica fora do select ou navega para outro campo.
No entanto, o evento @oninput é disparado imediatamente a cada alteração no valor do campo. No caso do InputSelect, isso significa que o evento @oninput será disparado assim que o usuário selecionar uma nova opção.
Exemplo Prático
Vamos imaginar o seguinte cenário com o seu EditForm:
O usuário clica no
InputSelect.O usuário seleciona um novo
Country.Imediatamente após a seleção, o Blazor dispara o evento
@oninput. A propriedadeCountryIddo seu modelo é atualizada. Se você tem uma lógica para recarregar dados baseados naCountryId(por exemplo, preencher umInputSelectde cidades), isso vai acontecer neste momento.O usuário clica fora do
InputSelect.Neste momento, o evento
@onchangeseria disparado. Mas como o valor já foi atualizado pelo@oninpute não houve mais nenhuma alteração, a "mudança" já foi processada.
Como o @oninput já fez a atualização do modelo e o Blazor já processou a renderização, o @onchange acaba não sendo "necessário" para o propósito de atualizar a sua UI, por isso, parece que ele não é gerado.
Qual usar?
Use
@onchangequando você precisa que a ação só aconteça após o usuário confirmar a seleção e sair do campo. Isso pode ser útil para validações mais pesadas ou lógicas de negócio que não precisam ser executadas a cada interação.Use
@oninput(o mais comum paraInputSelect) quando você precisa que a UI seja atualizada dinamicamente assim que a seleção for feita, como no seu caso, onde a mudança deCountrydeve afetar imediatamente outros campos do formulário.
Em resumo, o @oninput é a forma mais direta de reagir às seleções de um InputSelect no Blazor, e é por isso que ele funciona para o seu cenário. O @onchange tem um comportamento mais "tradicional" de HTML, disparando o evento apenas no "blur".
Mas, no meu caso, quando eu clico fora, fazendo o blur, e deixando o componente verde, o @oninput funciona. Se eu faço o mesmo no @onchange nada acontece, nenhuma das vezes. No DotNet 9 esse é o comportamento padrão?
Esse comportamento que você está descrevendo é uma peculiaridade do Blazor e não se restringe apenas ao .NET 9 — ele tem sido assim em versões anteriores também. A questão principal é a interação entre o data binding bidirecional (@bind-Value) e o evento @onchange.
O que está acontecendo
@bind-Valueé um "atalho": Quando você usa@bind-Value="MeuModelo.CountryId", o Blazor faz muito trabalho nos bastidores para você. Ele não apenas define o valor inicial doInputSelect, mas também configura uma lógica interna para atualizar a propriedadeMeuModelo.CountryIdsempre que o valor do componente de entrada muda.Uso do
@onchangeinterno: A maneira padrão do@bind-Valuede detectar mudanças em umInputSelecté através do evento@onchange. Acontece que o Blazor não permite que você tenha dois manipuladores para o mesmo evento. Como o@bind-Valuejá está usando o@onchangeinternamente para a sincronização, seu próprio manipulador@onchangeé ignorado. É como se houvesse uma fila, e a sua chamada para o evento ficasse de fora.@oninputnão é o mesmo evento: O evento@oninputé um evento de browser diferente do@onchange. Ele não faz parte do pacote@bind-Value. Por isso, o Blazor permite que você o utilize livremente. Quando você usa@oninput, ele é disparado e executa sua lógica, e o@bind-Valueainda consegue fazer o trabalho dele em paralelo, sem conflito.
Como o @oninput resolve o problema no seu caso?
Quando você seleciona uma nova opção no InputSelect, o navegador dispara o evento oninput. O Blazor escuta isso e executa o código que você associou a ele. Por sorte, o Blazor é rápido o suficiente para que essa execução e a re-renderização aconteçam quase que instantaneamente.
Quando você clica fora, o navegador dispara o evento onchange. Como o valor já foi processado e sincronizado pelo @oninput anteriormente, e o @bind-Value já usou esse valor, não há nada de "novo" para o seu manipulador @onchange fazer, mesmo que ele fosse disparado. O comportamento padrão do @onchange é realmente disparar somente quando o elemento perde o foco.
Soluções para usar o @onchange
Se você realmente precisa usar o @onchange por alguma razão (por exemplo, para ter a lógica de negócio separada da sincronização imediata), existem algumas abordagens:
Abandone o
@bind-Value: Você pode remover o@bind-Valuee gerenciar a sincronização manualmente. Neste caso, o seu@onchangeserá disparado, e você terá que atualizar a propriedade do seu modelo dentro do seu método.Razor CSHTML<InputSelect @onchange="HandleCountryChange" Value="@CountryId"> ... </InputSelect> @code { public int CountryId { get; set; } private void HandleCountryChange(ChangeEventArgs e) { if (int.TryParse(e.Value.ToString(), out var newCountryId)) { CountryId = newCountryId; // Sua lógica customizada aqui } } }Use o
EditContext.OnFieldChanged: Se você estiver dentro de umEditForm, pode se inscrever no eventoOnFieldChangeddoEditContextpara reagir a qualquer mudança em qualquer campo do formulário. Essa é uma abordagem mais poderosa e flexível para lidar com eventos de forma centralizada.
Em resumo, a razão pela qual seu @onchange não funciona diretamente no InputSelect com @bind-Value é porque o próprio @bind-Value já está usando esse evento. O @oninput funciona porque é um evento separado e permite que você reaja à mudança de valor imediatamente.
E, se eu usar @bind-Value tem alguma possibilidade de disparar um OnCountryChange sem usar o @onchange?
Sim, existe uma forma de disparar uma lógica customizada em resposta a uma mudança de valor usando @bind-Value, mas sem usar o evento @onchange diretamente no componente.
A melhor maneira de fazer isso é utilizando o @bind-Value:after. Essa é uma sintaxe especial do Blazor que permite que você execute um método logo após a propriedade ligada ser atualizada.
Como usar o @bind-Value:after
A ideia é que, quando o valor do seu InputSelect muda (disparado pelo @onchange interno do @bind-Value), o Blazor primeiro atualiza a propriedade do seu modelo (CountryId, no seu caso) e, depois, executa o método que você definiu com @bind-Value:after.
Veja um exemplo de como seria o seu código:
<InputSelect @bind-Value:after="OnCountryChange" @bind-Value="MeuModelo.CountryId">
@* Suas opções de países aqui *@
</InputSelect>
@code {
public CountryModel MeuModelo { get; set; } = new CountryModel();
private void OnCountryChange()
{
// Esta lógica será executada imediatamente após MeuModelo.CountryId ser atualizado.
Console.WriteLine($"O novo CountryId é: {MeuModelo.CountryId}");
// Você pode chamar outros métodos aqui para atualizar a UI ou carregar dados.
// Exemplo: CarregarCidades(MeuModelo.CountryId);
}
}
Por que essa abordagem é a melhor?
Evita Conflito de Eventos: Você continua usando o
@bind-Valuepara a sincronização automática de dados (a forma recomendada no Blazor), mas o seu métodoOnCountryChangeé chamado de forma separada, sem entrar em conflito com a lógica interna do Blazor.Código Mais Limpo: Não é necessário gerenciar a atualização da propriedade do modelo manualmente. A ligação bidirecional (
@bind-Value) faz isso por você.Comportamento Confiável: A lógica é executada apenas depois que a propriedade
CountryIdé atualizada, garantindo que você sempre trabalhe com o valor mais recente.
Essa é a maneira ideal e mais moderna de lidar com a necessidade de executar uma lógica customizada em resposta a uma mudança em um componente de entrada no Blazor, especialmente quando se usa @bind-Value.
Comentários