Modelo explícito e diretivas do template
Esta aula usa formulários reativos tipados de @angular/forms. O modelo FormControl ou FormGroup é declarado em TypeScript e ligado ao template pelas diretivas de ReactiveFormsModule. Num componente standalone, importar apenas a classe FormControl no ficheiro não disponibiliza [formControl] ao template: é preciso declarar a dependência apropriada em imports.
No exemplo, [formGroup] associa o grupo e formControlName identifica cada controlo pelo nome. A escolha deste modelo não significa que todas as aplicações devam abandonar outros modelos de formulários; permite tornar explícitos os contratos estudados.
Tipos, null e reset
Um FormControl inicializado com uma string infere normalmente string|null, porque reset() sem argumento pode produzir null. A opção nonNullable altera a tipagem e o comportamento: reset repõe o valor inicial. Não equivale a required, a uma string não vazia ou a validação de dados remotos.
Um controlo inicializado com null pode precisar de um tipo explícito como string|null para aceitar strings mais tarde. No exemplo, title tem default vazio e validators próprios; o reset devolve esse vazio e o campo volta a falhar as regras de presença e comprimento.
Atualização completa, parcial e forma dos dados
setValue num grupo exige um valor com a estrutura correspondente aos controlos. patchValue permite atualizar apenas a parte fornecida e pode deixar passar silenciosamente certos erros de estrutura. Para mudar title preservando recordId, uma atualização parcial é adequada; para verificar que uma resposta possui exatamente o modelo esperado, não uses a permissividade de patchValue como prova.
Valida o contrato externo antes de o aplicar. A escolha entre os métodos deve seguir a intenção: substituir um estado completo ou alterar campos conhecidos mantendo os restantes.
Regras locais e relações entre campos
Validators síncronos recebem um controlo e devolvem null quando não encontram erro ou um objeto ValidationErrors quando encontram. Devolver {valid: true} não é uma confirmação de sucesso nesse contrato. Quando a regra compara controlos irmãos, como início e fim de um intervalo, coloca a validação no FormGroup ancestral comum.
A política de apresentação é separada: mostrar uma mensagem após touched ou dirty evita apontar erros antes da interação apropriada. Alterar dinamicamente validators requer updateValueAndValidity para recalcular o estado; mudar apenas a mensagem não atualiza a validade.
Validação assíncrona e submissão pendente
Validators assíncronos devolvem uma Promise ou Observable com o resultado; o Observable deve completar. Angular só inicia esta fase após sucesso dos validators síncronos. Enquanto aguarda, o controlo pode estar pending, um estado diferente de valid e invalid.
Se a política exige validação concluída, impedir apenas invalid pode ser insuficiente: exige valid no ponto de admissão. Define também o tratamento de falhas de comunicação: não se deve apresentar disponibilidade confirmada apenas porque não foi possível consultar o serviço.
Mesmo uma verificação local concluída pode ficar desatualizada antes da gravação final.
Disabled e escolha do payload
Num grupo enabled, controlos disabled são omitidos de value, embora continuem presentes em controls. Por isso, o tipo agregado pode ser Partial e um campo pode estar undefined nessa leitura. getRawValue inclui os valores dos controlos disabled.
No exemplo, a chave R7 é necessária à recolha, pelo que o método escolhido corresponde ao contrato. A chave não se torna confiável por estar num campo desativado: o cliente pode ser alterado e o servidor continua responsável pela autorização e validade.
Decide explicitamente se um campo deve ser omitido, incluído ou calculado no destinatário.
Coleções, intenção e resumo
FormGroup representa um conjunto conhecido de nomes; FormArray representa controlos numa coleção indexada; FormRecord permite chaves abertas com controlos homogéneos. Não troques nomes de negócio por posições sem considerar identidade. O evento ngSubmit comunica a intenção de submeter e chama o handler associado; não comprova persistência.
No exemplo, o resultado é uma mensagem de recolha local e não existe chamada HTTP. Num fluxo completo, o estado deve distinguir validação, pedido em curso, rejeição e aceitação. Escolher uma opção sobre esses estados faz parte da avaliação, sem executar o exemplo.
// Reading example: local draft only; no API persistence or application bootstrap.
import {Component, signal} from '@angular/core';
import {FormControl, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
@Component({
selector: 'record-draft', standalone: true, imports: [ReactiveFormsModule],
template: `<form [formGroup]="form" (ngSubmit)="collect()">
<label>Record <input formControlName="recordId"></label>
<label>Title <input formControlName="title"></label>
@if (form.controls.title.invalid && form.controls.title.touched) {
<p>A title of at least three characters is required.</p>
}
<button type="submit" [disabled]="!form.valid">Collect draft</button>
<button type="button" (click)="form.reset()">Reset</button>
</form><p>{{ notice() }}</p>`,
})
export class RecordDraft {
readonly form = new FormGroup({
recordId: new FormControl({value: 'R7', disabled: true}, {nonNullable: true}),
title: new FormControl('', {
nonNullable: true, validators: [Validators.required, Validators.minLength(3)],
}),
});
readonly notice = signal('No draft collected');
collect() {
this.form.markAllAsTouched();
if (!this.form.valid) return;
const draft = this.form.getRawValue();
this.notice.set('Collected ' + draft.recordId + ': ' + draft.title);
}
}O grupo fica enabled porque title está enabled; recordId continua no modelo, embora esteja disabled. A recolha usa getRawValue para incluir os dois campos. O handler exige valid e a mensagem apenas confirma recolha local.
Reset repõe os valores iniciais dos controlos nonNullable; não deve ser interpretado como apagamento de dados já gravados noutro sistema.
Armadilhas comuns
Confundir nonNullable com required; esperar que disabled remova o controlo; usar patchValue como verificação de schema; aceitar pending como valid; devolver um objeto de sucesso num validator; interpretar ngSubmit como confirmação de gravação.
Tópicos relacionados: Signals, inputs e contratos de outputs · HTTP, streams e aceitação de respostas · Testar comportamentos e limites de integração
O formulário expressa um modelo local e uma intenção. Escolhe estrutura, reset, validação e recolha explicitamente, e só comunica sucesso remoto quando existir a evidência correspondente. Campos omitidos ou pending são estados a interpretar, não detalhes a ignorar.
Referência: Reactive model and template · Angular 22; standalone components