Eu tenho um elemento personalizado:
<div formControlName="surveyType">
<div *ngFor="let type of surveyTypes"
(click)="onSelectType(type)"
[class.selected]="type === selectedType">
<md-icon>{{ type.icon }}</md-icon>
<span>{{ type.description }}</span>
</div>
</div>
Quando tento adicionar o formControlName, recebo uma mensagem de erro:
Erro de erro: nenhum acessador de valor para o controle de formulário com o nome: 'surveyType'
Eu tentei adicionar ngDefaultControl
sem sucesso. Parece que é porque não há entrada / seleção ... e eu não sei o que fazer.
Gostaria de vincular meu clique a este formControl para que, quando alguém clicar em todo o cartão, forçar meu 'tipo' para o formControl. É possível?
Respostas:
Você pode usar
formControlName
apenas as diretivas implementadasControlValueAccessor
.Implementar a interface
Portanto, para fazer o que você deseja, é necessário criar um componente que implemente
ControlValueAccessor
, o que significa implementar as três funções a seguir :writeValue
(diz à Angular como gravar valor do modelo na visualização)registerOnChange
(registra uma função de manipulador chamada quando a exibição é alterada)registerOnTouched
(registra um manipulador a ser chamado quando o componente recebe um evento de toque, útil para saber se o componente foi focado).Registrar um provedor
Então, você deve informar ao Angular que essa diretiva é uma
ControlValueAccessor
(a interface não será cortada, uma vez que é retirada do código quando o TypeScript é compilado no JavaScript). Você faz isso registrando um provedor .O provedor deve fornecer
NG_VALUE_ACCESSOR
e usar um valor existente . Você também precisará de umforwardRef
aqui. Observe queNG_VALUE_ACCESSOR
deve ser um provedor múltiplo .Por exemplo, se sua diretiva personalizada se chama MyControlComponent, você deve adicionar algo ao longo das seguintes linhas dentro do objeto passado ao
@Component
decorador:Uso
Seu componente está pronto para ser usado. Com formulários controlados por modelo , a
ngModel
ligação agora funcionará corretamente.Com formulários reativos , agora você pode usar corretamente
formControlName
e o controle de formulário se comportará conforme o esperado.Recursos
fonte
Eu acho que você deve usar
formControlName="surveyType"
em uminput
e não em umdiv
fonte
<ng-content>
em um componente envoltório e deixar o componente pai que defineformControls
simplesmente colocar o <input> dentro do <invólucro>O erro significa que o Angular não sabe o que fazer quando você coloca um
formControl
adiv
. Para corrigir isso, você tem duas opções.formControlName
elemento em, que é suportado pelo Angular imediatamente. Essas são:input
,textarea
eselect
.ControlValueAccessor
interface. Ao fazer isso, você está dizendo ao Angular "como acessar o valor do seu controle" (daí o nome). Ou em termos simples: o que fazer, quando você coloca umformControlName
elemento, que naturalmente não tem um valor associado a ele.Agora, implementar a
ControlValueAccessor
interface pode ser um pouco assustador no começo. Especialmente porque não há muita documentação boa por aí e você precisa adicionar muita clichê no seu código. Então, deixe-me tentar detalhar isso em algumas etapas simples de seguir.Mova seu controle de formulário para seu próprio componente
Para implementar o
ControlValueAccessor
, você precisa criar um novo componente (ou diretiva). Mova o código relacionado ao seu controle de formulário para lá. Assim, também será facilmente reutilizável. Ter um controle já dentro de um componente pode ser o motivo, em primeiro lugar, porque você precisa implementar aControlValueAccessor
interface, caso contrário, não poderá usar seu componente personalizado junto com os formulários Angular.Adicione o boilerplate ao seu código
A implementação da
ControlValueAccessor
interface é bastante detalhada, aqui está o boilerplate que vem com ela:Então, o que as partes individuais estão fazendo?
ControlValueAccessor
interfaceControlValueAccessor
interfaceonChange
eonTouch
com sua própria implementação durante o tempo de execução, para que você possa chamar essas funções. Portanto, é importante entender este ponto: você não precisa implementar onChange e onTouch por conta própria (exceto a implementação vazia inicial). A única coisa que você faz com (c) é deixar o Angular anexar suas próprias funções à sua classe. Por quê? Portanto, você pode chamar os métodosonChange
eonTouch
fornecidos pela Angular no momento apropriado. Vamos ver como isso funciona abaixo.writeValue
método funciona na próxima seção, quando o implementarmos. Coloquei aqui, para que todas as propriedades necessáriasControlValueAccessor
sejam implementadas e seu código ainda seja compilado.Implementar writeValue
O que
writeValue
faz é fazer algo dentro do seu componente personalizado, quando o controle do formulário é alterado do lado de fora . Por exemplo, se você tiver nomeado seu componente de controle de formulário personalizadoapp-custom-input
e o estiver usando no componente pai, desta forma:então
writeValue
é acionado sempre que o componente pai, de alguma forma, altera o valor demyFormControl
. Pode ser, por exemplo, durante a inicialização do formulário (this.form = this.formBuilder.group({myFormControl: ""});
) ou em uma redefinição do formuláriothis.form.reset();
.O que você normalmente deseja fazer se o valor do controle de formulário for alterado externamente é gravá-lo em uma variável local que represente o valor do controle de formulário. Por exemplo, se você
CustomInputComponent
gira em torno de um controle de formulário baseado em texto, ele pode se parecer com o seguinte:e no html de
CustomInputComponent
:Você também pode gravá-lo diretamente no elemento de entrada, conforme descrito nos documentos angulares.
Agora você lidou com o que acontece dentro do seu componente quando algo muda fora. Agora vamos olhar para a outra direção. Como você informa o mundo exterior quando algo muda dentro do seu componente?
Chamando onChange
O próximo passo é informar o componente pai sobre alterações dentro do seu
CustomInputComponent
. É aqui que as funçõesonChange
eonTouch
de (c) de cima entram em cena. Ao chamar essas funções, você pode informar o exterior sobre alterações dentro do seu componente. Para propagar alterações do valor para o exterior, você precisa chamar onChange com o novo valor como argumento . Por exemplo, se o usuário digitar algo noinput
campo em seu componente personalizado, você chamaráonChange
com o valor atualizado:Se você verificar a implementação (c) de cima novamente, verá o que está acontecendo: Angular vincula sua própria implementação à
onChange
propriedade da classe. Essa implementação espera um argumento, que é o valor de controle atualizado. O que você está fazendo agora é chamar esse método e avisar a Angular sobre a mudança. Agora, o Angular vai em frente e altera o valor do formulário do lado de fora. Esta é a parte chave disso tudo. Você disse ao Angular quando deveria atualizar o controle de formulário e com qual valor chamandoonChange
. Você forneceu os meios para "acessar o valor do controle".A propósito: O nome
onChange
é escolhido por mim. Você pode escolher qualquer coisa aqui, por exemplopropagateChange
ou similar. No entanto, o nome que você escolher será a mesma função que recebe um argumento, que é fornecido pelo Angular e que é vinculado à sua classe peloregisterOnChange
método durante o tempo de execução.Chamando onTouch
Como os controles de formulário podem ser "tocados", você também deve fornecer ao Angular os meios para entender quando seu controle de formulário personalizado é tocado. Você pode fazer isso, adivinhou, chamando a
onTouch
função Portanto, para o nosso exemplo aqui, se você quiser se manter em conformidade com o modo como o Angular está fazendo isso nos controles prontos para uso, chameonTouch
quando o campo de entrada estiver embaçado:Novamente,
onTouch
é um nome escolhido por mim, mas sua função real é fornecida pelo Angular e leva zero argumentos. O que faz sentido, já que você está apenas informando a Angular, que o controle de formulário foi tocado.Juntando tudo
Então, como é que isso acontece quando tudo se junta? Deve ficar assim:
Mais exemplos
Formulários aninhados
Observe que os Controladores de Valor de Controle NÃO são a ferramenta certa para grupos de formulários aninhados. Para grupos de formulários aninhados, você pode simplesmente usar um
@Input() subform
. Os Controladores de Valor de Controle são feitos para quebrarcontrols
, nãogroups
! Veja este exemplo como usar uma entrada para um formulário aninhado: https://stackblitz.com/edit/angular-nested-forms-input-2Fontes
fonte
Para mim, foi devido ao atributo "múltiplo" no controle de entrada selecionado, pois o Angular possui diferentes ValueAccessor para esse tipo de controle.
E dentro do modelo use assim
Mais detalhes ref Documentos oficiais
fonte