Hey dev, usando Zod no Angular? Pra que?
No mundo do desenvolvimento javascript, a biblioteca Zod é uma poderosíssima ferramenta para validação de dados e formularios. Não seria nada legal salvar um NaN ou undefined no banco de dados né? né????
Pois bem, temos no mundo do React o costume de usar bibliotecas para tudo (afinal, React é uma casca vazia), e uma delas é o poderoso Zod para validação de formularios.
Observe o exemplo:
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
// Definindo o esquema de validação com Zod
const schema = z.object({
nome: z.string().min(2, 'Muito curto').max(50, 'Muito longo'),
sobrenome: z.string().min(2, 'Muito curto').max(50, 'Muito longo'),
senha: z.string().min(8, 'A senha deve ter pelo menos 8 caracteres'),
repetirSenha: z.string().refine((value) => value === senha, { message: 'As senhas precisam ser iguais' }),
});
export const Formulario = () => {
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: zodResolver(schema),
});
const onSubmit = (data) => {
console.log(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label>Nome:</label>
<input {...register("nome")} />
{errors.nome && <p>{errors.nome.message}</p>}
</div>
<div>
<label>Sobrenome:</label>
<input {...register("sobrenome")} />
{errors.sobrenome && <p>{errors.sobrenome.message}</p>}
</div>
<div>
<label>Senha:</label>
<input type="password" {...register("senha")} />
{errors.senha && <p>{errors.senha.message}</p>}
</div>
<div>
<label>Repetir Senha:</label>
<input type="password" {...register("repetirSenha")} />
{errors.repetirSenha && <p>{errors.repetirSenha.message}</p>}
</div>
<button type="submit">Enviar</button>
</form>
);
};
Podemos ter a mesma validação no backend na hora de receber um body http e vários outros exemplos. Mas porque citei o React? Porque o assunto está caminhando para o framework frontend Angular.
Então vamos lá, vamos criar um componente Angular independente usando um template string para montar o nosso html.
@Component({
selector: 'app-formulario',
standalone: true,
imports: [CommonModule, ReactiveFormsModule], //importando o ReactiveFormsModule no componente
template: `
<form
(submit)="enviarFormulario"
[formGroup]="form" // injetando o atributo form no formulario
*ngIf="form" // Utilizando o ngIf aqui para evitar erro ExpressionChangedAfterItHasBeenCheckedError
>
<div>
<label>Nome:</label>
<input type="text"
formControlName="nome" //Passando aqui um controle reativo no formulario
/>
</div>
<div>
<label>Sobrenome:</label>
<input type="text" formControlName="sobrenome" />
</div>
<div>
<label>Senha:</label>
<input type="password" formControlName="senha" />
</div>
<div>
<label>Repetir Senha:</label>
<input type="password" formControlName="repetirSenha" />
</div>
<button type="submit">Enviar</button>
</form>
`,
})
export class FormularioComponent {
public form!: FormGroup;
}
Ótimo, agora, pro nosso componente não fica muito extenso, vamos trabalhar dentro da classe omitindo o html. Mas lembre-se da regra dos decoratos senhor copia e cola que caiu aqui no desespero, decorator tem que ficar acima de uma classe.
Vamos começar instanciando o nosso grupo de formulario usando a classe FormGroup
. Dentro do objeto, vamos usar um objeto onde os valores são instancias de FormControl
.
Ficou dificil? Vamos tirar a prova no código!
obs: instanciamos os elementos no hook onInit
@Component({...})
export class FormularioComponent {
public form!: FormGroup;
ngOnInit(): void {
this.form = new FormGroup({
name: new FormControl('', [Validators.required, Validators.minLength(2)]),
sobrenome: new FormControl('', [Validators.required, Validators.maxLength(50)]),
senha: new FormControl('', [Validators.minLength(8), Validators.pattern(Regex)]),
repetirSenha: new FormControl('', [Validators.required, Validators.minLength(8)]),
})
}
}
Pronto! Agora nos temos um formulario reativo que acrecenta em cada atributo o input do formulário de forma reativa. Usamos também a classe estatica Validators
para usar validação nos campos de formulário.
Para encerar, vamos criar uma validação personalizada para verificar se o campo senha e repetirSenha são iguais.
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
export const MustMatch = (value: string, valueMatch: string): ValidatorFn | null => {
return (control: AbstractControl): ValidationErrors | null => {
const match = control.get(value);
const valueToMatch = control.get(valueMatch);
if (match?.value !== valueToMatch?.value) {
valueToMatch?.setErrors({ mustMatch: true });
return { mustMatch: true };
}
return null;
};
};
Agora vamos implementar no componente utilizando como segundo argumento de FormGroup.
@Component({...})
export class FormularioComponent {
ngOnInit(): void {
this.form = new FormGroup({...}, { validators: MustMatch('senha', 'repetirSenha') })
}
}
Ótimo, agora podemos acessar diretamente os erros de cada campo diretamente no html usando os getters da instancia de FormGroup.
<div *ngIf="form.get('password')?.invalid && form.get('senha')?.touched">
<div *ngIf="form.get('senha')?.errors?.['required']">Senha não pode ficar em branco</div>
<div *ngIf="form.get('senha')?.errors?.['minlength']">Senha muita curta</div>
<div *ngIf="form.get('senha')?.errors?.['mustMatch']">Senhas não são iguais </div>
<div *ngIf="form.get('senha')?.errors?.['pattern']">Senha não atende os requisitos </div>
</div>
É isso. O Angular faz o resto. E não precisamos instalar uma nova dependencia para isso.
Fale comigo na comunidade que participo