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
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.
<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.
<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:
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.
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:
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:
- explicar formulário, campo, label, valor, envio e validação;
- diferenciar validação HTML, Angular, API e banco;
- comparar Signal, Reactive e Template-driven Forms;
- criar um modelo tipado em signal;
- construir uma árvore com
form(); - conectar controles com
FormField; - declarar regras em um schema;
- ler estados
touched,dirty,valid,invalideerrors; - apresentar mensagens acessíveis no momento adequado;
- processar envio sem duplicidade;
- reconhecer
FormControleFormGrouptipados; - 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
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
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
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
protected readonly taskForm = form(this.taskModel);
A árvore espelha o objeto:
taskForm
├── title
├── description
├── priority
└── dueDate
5. Importando as diretivas
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
<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
<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
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()emaxLength();min()emax();email();pattern();validate()para regra personalizada.
Escolha a regra que expressa o contrato, não apenas a mensagem visual desejada.
10. Regra personalizada
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
taskForm.title().valid()
taskForm.title().invalid()
taskForm.title().errors()
Esses valores são signals. A raiz agrega os campos:
taskForm().valid()
12. Estado de interação
taskForm.title().touched()
taskForm.title().dirty()
touched: a pessoa focou e saiu do campo;dirty: a pessoa modificou o campo;untouchedepristine: 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
@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
<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
<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
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
<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:
<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
<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á:
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
<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
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.
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;
FormFieldeFormRoot;- 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
FormFieldeFormRoot. - 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;FormFieldconecta controles eFormRootcoordena 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.