# Aula 4.6 - Formulários e validação

## Identificação

- **Duração:** 2 horas
- **Tipo:** teoria aplicada e laboratório
- **Entrega:** cadastro de tarefa tipado e acessível
- **Laboratório:** [`../exemplos/aula-4.6/index.html`](../exemplos/aula-4.6/index.html)

## Introdução

### O que é um formulário?

Formulário é uma região da interface usada para coletar dados e enviá-los para uma
operação. Ele reúne campos, instruções, validações e uma ação de envio.

```html
<form>
  <label for="title">Título</label>
  <input id="title" name="title">
  <button type="submit">Salvar</button>
</form>
```

Na Knowledge AI, o formulário transformará o que a pessoa digita em um rascunho de
tarefa que depois será enviado à API.

### O que é um campo?

Campo é um controle que recebe ou apresenta um valor: `input`, `select`, `textarea`,
checkbox ou um componente de formulário personalizado. Cada campo deve ter nome,
tipo, valor e finalidade claros.

### Para que serve a `label`?

`label` identifica o campo. Ela ajuda todas as pessoas e é essencial para leitores de
tela. Clicar na label também move o foco para o controle associado.

```html
<label for="task-title">Título da tarefa</label>
<input id="task-title">
```

Placeholder não substitui label: ele desaparece ao digitar e não funciona como nome
confiável do campo.

### O que significa enviar o formulário?

Enviar é solicitar o processamento conjunto dos valores. Pressionar Enter em um
campo ou ativar o botão `type="submit"` dispara o envio. Por isso, escutamos o evento
do formulário, não apenas um clique específico.

### O que é validação?

Validação verifica se os dados respeitam regras antes de uma operação. Exemplos:

- título obrigatório;
- título entre 5 e 80 caracteres;
- prioridade entre as opções conhecidas;
- data não pode estar no passado.

Validação não serve apenas para mostrar texto vermelho. Ela protege contratos e ajuda
a pessoa a corrigir entradas.

### Validar no navegador é suficiente?

Não. Existem camadas complementares:

```text
HTML       → restrições básicas e semântica do controle
Angular    → retorno imediato e estado da interface
API NestJS → regra confiável antes de persistir
MySQL      → integridade final do armazenamento
```

Qualquer pessoa pode contornar o JavaScript do navegador. A API deve validar tudo
novamente. O front-end nunca se conecta diretamente ao MySQL.

### O que são Signal Forms?

Signal Forms são a API atual do Angular para criar formulários sobre um modelo
gravável com signals. No Angular 22, essa API está estável e oferece sincronização,
tipagem e validação baseada em schema.

```ts
readonly taskModel = signal<TaskDraft>({
  title: "",
  description: "",
  priority: "medium",
  dueDate: "",
});

readonly taskForm = form(this.taskModel);
```

O modelo é a fonte de verdade. O formulário cria uma árvore de campos com a mesma
estrutura.

### Signal Forms são a única forma de criar formulários Angular?

Não. O Angular oferece três abordagens:

| Abordagem | Fonte de verdade | Uso típico |
|---|---|---|
| Signal Forms | modelo em signal | aplicações novas com Angular 22+ |
| Reactive Forms | `FormControl` e `FormGroup` | sistemas existentes e formulários complexos |
| Template-driven | propriedades e `ngModel` | formulários simples |

Esta aula ensina Signal Forms como caminho atual e apresenta Reactive Forms para
leitura e manutenção de projetos existentes.

### O que é um schema de validação?

É a declaração centralizada das regras associadas aos caminhos do modelo:

```ts
readonly taskForm = form(this.taskModel, (path) => {
  required(path.title, { message: "Informe o título." });
  minLength(path.title, 5, { message: "Use pelo menos 5 caracteres." });
});
```

O schema não é o schema do MySQL. Ele descreve comportamento do formulário no
front-end.

### Como isso entra na Knowledge AI?

Criaremos um cadastro com título, descrição, prioridade e prazo. O formulário
mostrará erros depois da interação, acompanhará `touched`, `dirty`, `valid` e
`submitting`, impedirá envio duplicado e representará sucesso ou erro da futura API.

## Objetivos

Ao final da aula, o aluno deverá conseguir:

1. explicar formulário, campo, label, valor, envio e validação;
2. diferenciar validação HTML, Angular, API e banco;
3. comparar Signal, Reactive e Template-driven Forms;
4. criar um modelo tipado em signal;
5. construir uma árvore com `form()`;
6. conectar controles com `FormField`;
7. declarar regras em um schema;
8. ler estados `touched`, `dirty`, `valid`, `invalid` e `errors`;
9. apresentar mensagens acessíveis no momento adequado;
10. processar envio sem duplicidade;
11. reconhecer `FormControl` e `FormGroup` tipados;
12. preparar os dados para a API sem confiar apenas no front-end.

## Pré-requisitos

- Aulas 4.1 a 4.5 concluídas;
- componentes, templates, bindings e signals;
- interfaces TypeScript;
- serviços e injeção de dependência;
- HTML semântico e acessibilidade básica.

## Pergunta orientadora

> Como coletar dados com segurança, boa experiência e um contrato tipado que continue válido no servidor?

## Roteiro sugerido

| Etapa | Duração |
|---|---:|
| Anatomia e camadas de validação | 20 min |
| Modelo, campos e bindings | 25 min |
| Schema e estados | 30 min |
| Acessibilidade e envio | 25 min |
| Laboratório | 15 min |
| Revisão | 5 min |

## 1. Modelo do formulário

```ts
type TaskPriority = "low" | "medium" | "high";

interface TaskDraft {
  title: string;
  description: string;
  priority: TaskPriority;
  dueDate: string;
}
```

O modelo representa somente dados editáveis. Identificador, data de criação e usuário
responsável normalmente são definidos fora deste formulário.

## 2. Valores iniciais definidos

```ts
const EMPTY_TASK: TaskDraft = {
  title: "",
  description: "",
  priority: "medium",
  dueDate: "",
};
```

Signal Forms usa objetos e arrays simples na estrutura. Inicialize todos os campos.
`undefined` representa ausência de campo, não apenas um valor vazio.

## 3. Criando o modelo gravável

```ts
protected readonly taskModel = signal<TaskDraft>({ ...EMPTY_TASK });
```

O signal guarda a fonte de verdade. Alterações nos controles atualizam o modelo e
alterações no modelo refletem nos controles.

## 4. Criando a árvore de campos

```ts
protected readonly taskForm = form(this.taskModel);
```

A árvore espelha o objeto:

```text
taskForm
├── title
├── description
├── priority
└── dueDate
```

## 5. Importando as diretivas

```ts
import { form, FormField, FormRoot } from "@angular/forms/signals";

@Component({
  imports: [FormField, FormRoot],
})
```

`FormField` conecta controles à árvore. `FormRoot` coordena o envio do formulário.

## 6. Ligando um campo

```html
<label for="task-title">Título</label>
<input
  id="task-title"
  type="text"
  [formField]="taskForm.title"
>
```

`[formField]` sincroniza valor e estados. Não precisamos implementar manualmente um
manipulador de `input` para cada controle.

## 7. Ligando select e textarea

```html
<select id="priority" [formField]="taskForm.priority">
  <option value="low">Baixa</option>
  <option value="medium">Média</option>
  <option value="high">Alta</option>
</select>

<textarea id="description" [formField]="taskForm.description"></textarea>
```

Os valores dos `option` precisam corresponder ao tipo aceito pelo modelo.

## 8. Schema de validação

```ts
protected readonly taskForm = form(this.taskModel, (path) => {
  required(path.title, { message: "Informe o título." });
  minLength(path.title, 5, { message: "Use pelo menos 5 caracteres." });
  maxLength(path.title, 80, { message: "Use no máximo 80 caracteres." });
});
```

O callback configura a lógica uma vez. As regras reagem às mudanças de valor.

## 9. Validadores incorporados

Signal Forms possui regras como:

- `required()`;
- `minLength()` e `maxLength()`;
- `min()` e `max()`;
- `email()`;
- `pattern()`;
- `validate()` para regra personalizada.

Escolha a regra que expressa o contrato, não apenas a mensagem visual desejada.

## 10. Regra personalizada

```ts
validate(path.dueDate, ({ value }) => {
  const dueDate = value();
  if (!dueDate) return null;

  return dueDate < todayAsIsoDate()
    ? { kind: "pastDate", message: "O prazo não pode estar no passado." }
    : null;
});
```

Retorne um objeto de erro quando inválido e `null` quando válido. Compare datas no
formato e fuso corretos para o domínio.

## 11. Estado de validação

```ts
taskForm.title().valid()
taskForm.title().invalid()
taskForm.title().errors()
```

Esses valores são signals. A raiz agrega os campos:

```ts
taskForm().valid()
```

## 12. Estado de interação

```ts
taskForm.title().touched()
taskForm.title().dirty()
```

- `touched`: a pessoa focou e saiu do campo;
- `dirty`: a pessoa modificou o campo;
- `untouched` e `pristine`: estados opostos conceituais.

Um campo pode estar inválido antes de qualquer interação. Isso não significa que a
mensagem deva aparecer imediatamente.

## 13. Quando mostrar erros

```html
@if (taskForm.title().touched() && taskForm.title().invalid()) {
  <ul id="title-errors">
    @for (error of taskForm.title().errors(); track error.kind) {
      <li>{{ error.message }}</li>
    }
  </ul>
}
```

Depois de uma tentativa de envio, os campos inválidos também devem revelar seus
erros. `FormRoot` e `submit()` ajudam marcando campos interativos como touched.

## 14. Associação acessível do erro

```html
<input
  id="task-title"
  [formField]="taskForm.title"
  [attr.aria-invalid]="taskForm.title().invalid()"
  aria-describedby="title-help title-errors"
>
<p id="title-help">Entre 5 e 80 caracteres.</p>
```

`aria-describedby` conecta instruções e erros ao campo. `aria-invalid` comunica o
estado; não substitui a mensagem textual.

## 15. Não dependa apenas de cor

Use texto, ícone textual ou estrutura além da borda vermelha. Pessoas com diferentes
formas de percepção precisam compreender o problema sem distinguir uma cor.

## 16. Formulário raiz

```html
<form [formRoot]="taskForm">
  <!-- campos -->
  <button type="submit">Salvar tarefa</button>
</form>
```

`FormRoot` previne o envio tradicional, configura `novalidate` e executa a ação de
submissão definida no formulário.

## 17. Configurando a submissão

```ts
protected readonly taskForm = form(
  this.taskModel,
  taskSchema,
  {
    submission: {
      action: async (field) => {
        await this.taskStore.create(field().value());
      },
    },
  },
);
```

A ação só executa quando a validação permite. Enquanto aguarda, o estado de
submissão fica ativo.

## 18. Impedindo envio duplicado

```html
<button type="submit" [disabled]="taskForm().submitting()">
  @if (taskForm().submitting()) {
    Salvando...
  } @else {
    Salvar tarefa
  }
</button>
```

Desabilite durante a operação, não apenas porque o formulário ainda está inválido.
Permitir a tentativa de envio ajuda a revelar erros para quem não percorreu todos os
campos.

## 19. Foco após erro

Quando o envio falha, mova o foco para o primeiro campo inválido ou para um resumo de
erros. Signal Forms oferece associação com o controle para ajudar a direcionar o
foco. Não mova o foco a cada caractere digitado.

## 20. Sucesso e erro global

Mensagens devem usar regiões vivas com moderação:

```html
<p role="status">Tarefa salva com sucesso.</p>
<p role="alert">Não foi possível salvar. Tente novamente.</p>
```

`status` é adequado para confirmação não urgente; `alert` para erro que exige
atenção. Não use `alert()` do navegador como experiência principal.

## 21. Erros do servidor

A API pode rejeitar dados que passaram no front-end: título duplicado, permissão
insuficiente ou conflito de versão. Mostre o erro no campo correspondente quando
possível e mantenha um resumo global para falhas gerais.

Não substitua a mensagem do servidor cegamente. Mapeie códigos conhecidos para textos
seguros e compreensíveis.

## 22. Reset consciente

Após sucesso, redefina o modelo apenas se isso fizer sentido para o fluxo. Em edição,
navegar para o detalhe pode ser melhor. Em cadastro contínuo, limpar e focar o
primeiro campo pode ser adequado.

Não apague os dados quando a API falhar.

## 23. HTML nativo continua importante

```html
<input type="text" required minlength="5" maxlength="80">
```

Signal Forms espelha algumas restrições em atributos nativos para comportamento e
acessibilidade. O estado suportado continua vindo da árvore do formulário, não de
`:invalid` ou `validationMessage` do navegador.

## 24. `novalidate` não significa sem validação

Ele desliga a interface automática de validação do navegador para que o Angular
controle mensagens e submissão consistentemente. As regras Angular e do servidor
continuam obrigatórias.

## 25. Reactive Forms tipados

Em projetos existentes, você encontrará:

```ts
readonly taskForm = new FormGroup({
  title: new FormControl("", {
    nonNullable: true,
    validators: [Validators.required, Validators.minLength(5)],
  }),
  priority: new FormControl<TaskPriority>("medium", {
    nonNullable: true,
  }),
});
```

`FormControl` guarda valor e estado; `FormGroup` organiza controles. A tipagem estrita
evita muitos casts e erros de nomes.

## 26. Template equivalente em Reactive Forms

```html
<form [formGroup]="taskForm" (ngSubmit)="save()">
  <input formControlName="title">
  <select formControlName="priority"></select>
  <button type="submit">Salvar</button>
</form>
```

O componente importa `ReactiveFormsModule`. Não misture abordagens no mesmo
formulário sem uma razão arquitetural clara.

## 27. Signal Forms ou Reactive Forms?

Use Signal Forms quando:

- a aplicação é nova e usa Angular 22+;
- signals já organizam o estado;
- tipagem inferida pelo modelo é desejada;
- schemas combinam com o projeto.

Use Reactive Forms quando:

- a base existente já os utiliza;
- há formulários dinâmicos complexos consolidados;
- a equipe depende de APIs e bibliotecas reativas existentes;
- estabilidade entre versões anteriores é necessária.

Consistência da base importa mais que trocar tecnologia por novidade.

## 28. Dados do formulário não são entidade pronta

```ts
interface TaskDraft {
  title: string;
  description: string;
  priority: TaskPriority;
  dueDate: string;
}
```

A API define autor, identificador e timestamps. Não envie campos administrativos
simplesmente porque apareceram no objeto do navegador.

## 29. Laboratório guiado

Abra o [cadastro acessível de tarefas](../exemplos/aula-4.6/index.html).

### Etapa 1 — Envie vazio

Observe o resumo e o foco no primeiro campo inválido.

### Etapa 2 — Corrija cada campo

Acompanhe valor, `touched`, `dirty` e `valid` no painel de estado.

### Etapa 3 — Teste a data

Informe uma data passada e observe a regra personalizada.

### Etapa 4 — Simule falha da API

Confirme que os valores permanecem para correção ou nova tentativa.

### Etapa 5 — Salve com sucesso

O botão representa submissão e evita uma segunda operação simultânea.

### Etapa 6 — Examine a fonte

Compare modelo, schema, template e versão Reactive Forms.

## 30. Sobre o laboratório estático

O laboratório reproduz estados de um formulário para abrir sem instalar Angular. A
pasta `src/app` contém Signal Forms equivalentes. A prévia usa APIs seguras do DOM e
simula a resposta da futura API sem persistir dados.

## 31. Erros comuns

### Usar placeholder como label

O campo perde seu nome visível durante a digitação.

### Mostrar todos os erros ao abrir

A interface começa acusando uma pessoa que ainda não interagiu.

### Validar somente no Angular

Requisições podem ser enviadas fora da interface.

### Desabilitar sempre o botão inválido

A pessoa pode não descobrir por que não consegue avançar.

### Apagar valores após erro da API

Isso obriga a redigitar e pode causar perda de trabalho.

### Usar `click` em vez de submit

Enter e outras formas de envio deixam de funcionar corretamente.

### Confiar no tipo TypeScript em ambiente de execução

Tipos desaparecem na execução. Dados externos precisam de validação real.

### Misturar Signal e Reactive Forms sem necessidade

A fonte de verdade e os estados ficam difíceis de acompanhar.

## 32. Boas práticas

- use elementos nativos e labels explícitas;
- escolha tipos de input adequados;
- mantenha um modelo de formulário próprio;
- centralize regras no schema;
- mostre erros após interação ou tentativa de envio;
- associe ajuda e erro com `aria-describedby`;
- não comunique erro apenas por cor;
- permita envio para revelar problemas;
- impeça duplicidade enquanto salva;
- preserve valores em falhas;
- valide novamente na API;
- mantenha o MySQL inacessível ao navegador.

## 33. Exercícios

### Exercício 1 — Labels

Crie campos de nome e e-mail com labels, ajuda e identificadores únicos.

### Exercício 2 — Schema

Valide título obrigatório entre 5 e 80 caracteres.

### Exercício 3 — Estados

Mostre mensagens apenas quando o campo estiver touched e invalid.

### Exercício 4 — Regra personalizada

Impeça uma data de término anterior à data de início.

### Exercício 5 — Reactive Forms

Reescreva dois campos com `FormGroup` e controles `nonNullable`.

## 34. Desafio

Crie um formulário de projeto com:

- modelo tipado em signal;
- pelo menos quatro campos;
- labels e instruções;
- `FormField` e `FormRoot`;
- regras obrigatória, tamanho e personalizada;
- mensagens após interação;
- resumo no envio inválido;
- foco no primeiro erro;
- estado de submissão;
- tratamento de erro de campo e global;
- confirmação acessível;
- mapeamento explícito para o DTO da API.

## 35. Lista de verificação de conclusão

- [ ] Explico formulário, campo, label e envio.
- [ ] Diferencio as camadas de validação.
- [ ] Comparo as três abordagens Angular.
- [ ] Crio modelo e árvore tipados.
- [ ] Uso `FormField` e `FormRoot`.
- [ ] Declaro schema de validação.
- [ ] Leio estados de interação e validade.
- [ ] Associo mensagens aos campos.
- [ ] Trato envio inválido e duplicidade.
- [ ] Reconheço Reactive Forms tipados.
- [ ] Sei que a API precisa validar novamente.

## 36. Critérios de avaliação

| Critério | Pontos |
|---|---:|
| Modelo e bindings tipados | 20 |
| Schema e regras | 25 |
| Estados e mensagens | 20 |
| Envio e tratamento de falhas | 15 |
| Acessibilidade | 15 |
| Organização | 5 |
| **Total** | **100** |

## Resumo

Nesta aula, aprendemos que:

- formulários coletam dados como uma operação coerente;
- label nomeia o campo e placeholder não a substitui;
- validações do navegador, Angular, API e banco são complementares;
- Signal Forms estão estáveis no Angular 22 para aplicações novas;
- o modelo em signal é a fonte de verdade;
- `form()` cria uma árvore tipada;
- `FormField` conecta controles e `FormRoot` coordena o envio;
- schemas centralizam regras;
- estados orientam quando mostrar mensagens;
- Reactive Forms continuam uma escolha sólida;
- o servidor nunca deve confiar apenas na validação do front-end.

## Próxima aula

Na Aula 4.7, conectaremos o serviço à API usando HTTP e RxJS, representando
carregamento, sucesso, vazio e erro.

## Fontes oficiais

- [Visão geral de Signal Forms](https://angular.dev/guide/forms/signals/overview)
- [Modelos de Signal Forms](https://angular.dev/guide/forms/signals/models)
- [Validação em Signal Forms](https://angular.dev/guide/forms/signals/validation)
- [Estado dos campos](https://angular.dev/guide/forms/signals/field-state-management)
- [Submissão de formulários](https://angular.dev/guide/forms/signals/form-submission)
- [Comparação das abordagens](https://angular.dev/guide/forms/signals/comparison)
- [Reactive Forms](https://angular.dev/guide/forms/reactive-forms)
