Entendendo o ControlValueAccessor
Tags: webdev, braziliandevs, angular, frontend
Published: 2024-04-20 04:10:36

## Introdução ao ControlValueAccessor
[código do projeto](https://github.com/brunoredes/contact-us-example)
Formulários dentro do Angular podem ser tratados utilizando duas abordagens: Reactive forms ou Template Driven forms. Essas abordagens nos permitem capturar entrada de dados, validar campos que possam estar inválidos, e são baseados em um objeto que será enviado ao servidor.

Pensando, tanto em usar reactive forms ou template driven: quando componentizamos muitos elementos, podemos acabar sentindo uma dificuldade em: "como podemos criar um componente apartado de um formulário, e continuar tendo a referência do dado digitado pelo usuário, via bind, em nosso `[(ngModel)]` ou no `formControlName`"?

_aka: como crio um control personalizado para o meu formulário_

É aí que entra o **PODEROSO** `ControlValueAccessor`!

### O que é o ControlValueAccessor

MAS O QUE É ESSE ControlValueAccessor???
![Personagem link de the legend of zelda, caindo na água sofrendo dano, fazendo alusão a um sentimento de "talvez isso seja complexo"](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f12op5kcsjd9wk8zv9tk.gif)

O [ControlValueAccessor](https://angular.io/api/forms/ControlValueAccessor) é uma interface que atua como uma ponte entre  elementos do DOM e a API de formulário do Angular. Implementando essa interface em um componente, podemos manipular valores através das propriedades dispostas pelo próprio `FormsModule`: `ngModel`; Ou no caso de um formulário reativo, o `formControlName`.



![Gato fazendo um joia, imagem faz alusão a um sentimento de "entendi"](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xsxa1gna6zox2eztfxg7.jpg)


Os métodos obrigatórios a serem implementados dentro da classe são os seguintes:

```typescript
interface ControlValueAccessor {
writeValue(obj: any): void
registerOnChange(fn: any): void
registerOnTouched(fn: any): void
setDisabledState(isDisabled: boolean)?: void
}
```

- O `writeValue` escreve um novo valor ao elemento
- O `registerOnChange` registra uma função callback que é chamada quando o valor do nosso controle muda na tela.
- O `registerOnTouched` serve para atualizar o estado do formulário para `touched` (ou blurred). Então, assim que o usuário interage com nosso elemento no controle personalizado, podemos chamar a função salva no callback, para informar ao Angular que o controle recebeu uma alteração.
- O `setDisabledState` vai ser chamado para avisar a API de formulários, que o controle sofreu alteração no estado de desabilitado (true para false, ou false para true).


![Uma pessoa surpresa](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lzdue5k98p821sc1ls8z.gif)

Agora que entendemos como funciona a interface `ControlValueAccessor`, e quais métodos ela implementa, vamos ao nosso código.


## Formulário de "Contact us"

Vamos criar um formulário básico, sobre "Contact us". É um formulário simples, onde teremos dois inputs e um textarea:
- Digite seu nome;
- Digite seu e-mail;
- Digite uma mensagem;

O formulário será feito com template-driven e com Reactive forms.
### Template Driven

// template-driven-form HTML
```HTML
<form (ngSubmit)="submit()">
  <fieldset class="">
    <legend>Fale conosco</legend>
    <div class="data">
      <label for="yourName">Digite seu nome</label>
      <input
        type="text"
        id="yourName"
        name="yourName"
        [(ngModel)]="contactUs.name"
        placeholder="DevTo da Silva"
      />
    </div>
    <div class="data">
      <label for="yourEmail">Digite seu email</label>
      <input
        type="email"
        id="yourEmail"
        name="yourEmail"
        [(ngModel)]="contactUs.email"
        placeholder="example@mail.com"
      />
    </div>

    <div class="data">
      <label for="message">Deixe sua mensagem</label>
      <textarea
        name="message"
        id="message"
        cols="30"
        rows="10"
        [(ngModel)]="contactUs.message"
      ></textarea>
    </div>
  </fieldset>

  <button
    type="submit"
    aria-label="Enviar o motivo do contato"
    aria-describedby="sendContactUs"
  >
    Enviar
  </button>
  <span [hidden]="true" id="sendContactUs"
    >Ao clicar no botão, você envia os dados que foram preenchidos. Em breve retornaremos o contato.</span>
</form>

<pre>{{contactUs | json}}</pre>
```

e o nosso component .ts

// template-driven-form HTML
```typescript
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { JsonPipe } from '@angular/common';

type ContactUsTemplateDriven = {
  name: string;
  email: string;
  message: string;
};

@Component({
  selector: 'app-template-driven-form-contactus',
  standalone: true,
  imports: [FormsModule, JsonPipe],
  templateUrl: './template-driven-form-contactus.component.html',
  styleUrl: './template-driven-form-contactus.component.scss',
})
export class TemplateDrivenFormContactusComponent {
  protected contactUs: ContactUsTemplateDriven = {
    name: '',
    email: '',
    message: '',
  };

  protected submit(): void {
    console.log('enviou', this.contactUs);
  }
}

```

### Formulário Reativo


// reactive-forms-contactus.component
```HTML
<form (ngSubmit)="submit()" [formGroup]="contactUsForm">
  <fieldset class="">
    <legend>Fale conosco</legend>
    <div class="data">
      <label for="name">Digite seu nome</label>
      <input
        type="text"
        id="name"
        name="name"
        formControlName="name"
        placeholder="DevTo da Silva"
      />
    </div>
    <div class="data">
      <label for="email">Digite seu email</label>
      <input
        type="email"
        id="email"
        name="email"
        formControlName="email"
        placeholder="example@mail.com"
      />
    </div>

    <div class="data">
      <label for="message">Deixe sua mensagem</label>
      <textarea name="message" id="message" cols="30" rows="10" formControlName="message"></textarea>
    </div>
  </fieldset>

  <button
    type="submit"
    aria-label="Enviar o motivo do contato"
    aria-describedby="sendContactUs"
  >
    Enviar
  </button>
  <span [hidden]="true" id="sendContactUs">
    Ao clicar no botão, você envia os dados que foram preenchidos. E assim que
    pudermos, retornaremos o contato
  </span>
</form>

<pre>{{contactUsForm.getRawValue() | json}}</pre>
```

// reactive-forms-contactus.component
```typescript
type ContactUsReactiveForm = {
  name: FormControl<string>;
  email: FormControl<string>;
  message: FormControl<string>;
};

@Component({
  selector: 'app-reactive-form-contactus',
  standalone: true,
  imports: [FormsModule, ReactiveFormsModule, JsonPipe],
  templateUrl: './reactive-form-contactus.component.html',
  styleUrl: './reactive-form-contactus.component.scss',
})
export class ReactiveFormContactusComponent {
  private readonly formBuilder = inject(NonNullableFormBuilder);

  protected contactUsForm!: FormGroup<ContactUsReactiveForm>;

  constructor() {
    this.contactUsForm = this.formBuilder.group<ContactUsReactiveForm>({
      name: this.formBuilder.control({ value: '', disabled: false }),
      email: this.formBuilder.control(
        { value: '', disabled: false },
        { validators: [Validators.email] }
      ),
      message: this.formBuilder.control({ value: '', disabled: false }),
    });
  }

  protected submit() {
    console.log('enviou', this.contactUsForm.getRawValue());
  }
}
```

Agora que finalizamos nossos formulários, vamos criar nossos controles personalizados.

Iremos criar três componentes, que serão os dois inputs e o textarea.


### Criando controles personalizados com ControlValueAccessor

Vamos criar um novo componente, utilizando o comando do angular-cli: 

```bash
$ ng g c components/contact-us-name-input
```

![contact-us-name-input, o componente que criamos mostrando três arquivos: .ts, .html e .scss](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vtw6o0sj9ooazh49ocf4.png)


Dentro do nosso componente, iremos implementar a interface ControlValueAccessor e seus respectivos métodos. Porém, só implementar a interface ControlValueAccessor não é o suficiente.
Precisamos também, registrar dentro dos providers do nosso component, o token `NG_VALUE_ACCESSOR`. Esse token é responsável pela integração do component, com a API de formulários do Angular.

Junto do NG_VALUE_ACCESSOR, também colocaremos o `forwardRef`. O uso do forwardRef se faz necessário porque estamos fazendo referência ao componente que estamos criando (nesse caso, para o "ContactUsNameInputComponent". Porém, faremos para todos os nossos controles personalizados.)


```typescript
@Component({
  selector: 'app-contact-us-name-input',
  standalone: true,
  imports: [],
  templateUrl: './contact-us-name-input.component.html',
  styleUrl: './contact-us-name-input.component.scss',
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => ContactUsNameInputComponent),
      multi: true,
    },
  ],
})
export class ContactUsNameInputComponent implements ControlValueAccessor {
  @Input() public contactName = '';

  protected value = '';
  protected disabled = false;

  protected onChanged!: (value: string) => void;
  protected onTouched!: () => void;

  writeValue(value: string): void {
    this.value = value;
  }
  registerOnChange(fn: (value: string) => void): void {
    this.onChanged = fn;
  }
  registerOnTouched(fn: () => void): void {
    this.onTouched = fn;
  }

  setDisabledState?(isDisabled: boolean): void {
    this.disabled = isDisabled;
  }
}

```

```html
<label for="name">Digite seu nome</label>
<input
  type="text"
  id="name"
  placeholder="DevTo da Silva"
  [value]="value"
  (input)="onChanged($any($event.target)?.value)"
/>
```

Vamos analisar o nosso código:
- Registramos dentro dos providers do nosso component, o NG_VALUE_ACCESSOR e fizemos referência ao nosso componente.

Nota: _Sempre precisamos fazer referência, no forwardRef, à classe que estamos trabalhando._

- Implementamos os métodos obrigatórios da interface ControlValueAccessor, tipamos os parâmetros dos métodos da interface, e atribuímos os valores aos atributos do componente.

- No nosso template html, colocamos um evento onInput, chamado o callback onChanged para escutar as mudanças que ocorrem no nosso input, e informar ao nosso formulário que as mudanças ocorreram


Tendo criado nosso primeiro componente, vamos implementá-lo em nossos componentes de formulário da seguinte forma:

Substituiremos a parte do label e input de nome, pelo nosso componente:

```html
<form (ngSubmit)="submit()">
  <fieldset class="">
    <legend>Fale conosco</legend>
    <div class="data">
// essa parte aqui
// pelo nosso novo componente
     <app-contact-us-name-input
        name="yourName"
        [contactName]="contactUs.name"
        [(ngModel)]="contactUs.name"
        [ngModelOptions]="{ standalone: true }"
      />
    </div>
... rest of html
```


![controle componentizado](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e7vadamfdcfwcede93nr.png)


Como estamos trabalhando com standalone components, precisamos informar à instância do NgModel que o nosso control também é standalone.


Faremos o mesmo trabalho dentro do input de email, e o textarea, registrando o `NG_VALUE_ACCESSOR`, e fazendo uso do forwardRef para referência dos nossos componentes. (para não ficar muito extenso e repetitivo, o código está no meu github, e você poder ver tudo que foi feito nesse link ao lado: [ContactUs - Github](https://github.com/brunoredes/contact-us-example).

Nosso formulário, agora com nossos controles personalizados integrados, ficará dessa forma:

```html
<form (ngSubmit)="submit()">
  <fieldset class="">
    <legend>Fale conosco</legend>
    <div class="data">
      <app-contact-us-name-input
        name="yourName"
        [contactName]="contactUs.name"
        [(ngModel)]="contactUs.name"
        [ngModelOptions]="{ standalone: true }"
      />
    </div>
    <div class="data">
      <app-contact-us-email-input
        name="email"
        [contactEmail]="contactUs.email"
        [(ngModel)]="contactUs.email"
        [ngModelOptions]="{ standalone: true }"
      />
    </div>

    <div class="data">
      <app-contact-us-message-text
        [(ngModel)]="contactUs.message"
        [ngModelOptions]="{ standalone: true }"
        [contactText]="contactUs.message"
      />
    </div>
  </fieldset>

  <button
    type="submit"
    aria-label="Enviar o motivo do contato"
    aria-describedby="sendContactUs"
  >
    Enviar
  </button>
  <span [hidden]="true" id="sendContactUs"
    >Ao clicar no botão, você envia os dados que foram preenchidos. E assim que
    pudermos, retornaremos o contato</span
  >
</form>

<pre>{{ contactUs | json }}</pre>
```


e nosso formulário reativo:
```html
<form (ngSubmit)="submit()" [formGroup]="contactUsForm">
  <fieldset class="">
    <legend>Fale conosco</legend>
    <div class="data">
      <app-contact-us-name-input formControlName="name" />
    </div>
    <div class="data">
      <app-contact-us-email-input formControlName="email" />
    </div>
    <div class="data">
      <app-contact-us-message-text formControlName="message" />
    </div>
  </fieldset>

  <button
    type="submit"
    aria-label="Enviar o motivo do contato"
    aria-describedby="sendContactUs"
  >
    Enviar
  </button>
  <span [hidden]="true" id="sendContactUs">
    Ao clicar no botão, você envia os dados que foram preenchidos. E assim que
    pudermos, retornaremos o contato
  </span>
</form>

<pre>{{ contactUsForm.getRawValue() | json }}</pre>
```

## Conclusão
Conseguimos entender como criar controles customizados, para serem reutilizados dentro de nossos formulários, independente da abordagem que foi utilizada. Também foi mostrado como usamos a interface `ControlValueAccessor`, entendemos também o uso do token `NG_VALUE_ACCESSOR`, e como fazer a referência do nosso componente enquanto ele não está definido, utilizando forwardRef.