Angular 21 de Cero a Experto / Formularios
Validaciones: integradas, custom y asíncronas
Un formulario sin validación es una puerta abierta a datos corruptos: montos negativos, emails inventados, campos vacíos. Angular trae validadores integrados para lo común y te deja escribir los tuyos para tus reglas de negocio. Hoy dominas las tres capas: validadores de fábrica, validadores CUSTOM (funciones puras que puedes testear en vivo) y validación asíncrona (consultar el servidor). La garantía de que solo entren datos correctos.
El inspector en la puerta
Validadores integrados
Angular incluye los más comunes en la clase Validators:
import { Validators } from '@angular/forms';
this.fb.group({
nombre: ['', [Validators.required, Validators.minLength(2)]],
valor: [0, [Validators.required, Validators.min(1), Validators.max(10_000_000)]],
email: ['', [Validators.required, Validators.email]],
telefono: ['', Validators.pattern(/^3\d{9}$/)], // celular colombiano
});Cada validador es una función que Angular ejecuta sobre el control. Si el valor no cumple, el
control queda inválido y expone el error en control.errors (por ejemplo { required: true }
o { min: { min: 1, actual: 0 } }).
Validadores custom: tu propia regla
Un validador es simplemente una función ValidatorFn: recibe el control y devuelve null si
todo está bien, o un objeto de error si no. Esa función es TypeScript puro —testeable en
aislamiento—:
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';
// regla de negocio: el nombre no puede ser solo espacios
export function noSoloEspacios(): ValidatorFn {
return (control: AbstractControl): ValidationErrors | null => {
const valor = (control.value ?? '') as string;
return valor.trim().length === 0 ? { soloEspacios: true } : null;
};
}
// úsalo como cualquier validador integrado:
this.fb.group({
nombre: ['', [Validators.required, noSoloEspacios()]],
});La convención: devolver null = válido; devolver { nombreDelError: ... } = inválido. El
patrón "función que devuelve un validador" (una fábrica) permite parametrizarlo —por ejemplo,
montoMaximo(limite)—.
Validación a nivel de grupo (entre campos)
Algunas reglas involucran VARIOS campos: "la confirmación de contraseña debe coincidir", "la
fecha fin debe ser posterior a la fecha inicio". Esos validadores van en el FormGroup, no en
un control:
export function clavesCoinciden(): ValidatorFn {
return (group: AbstractControl): ValidationErrors | null => {
const clave = group.get('clave')?.value;
const confirmar = group.get('confirmar')?.value;
return clave === confirmar ? null : { noCoinciden: true };
};
}
this.fb.group(
{ clave: ['', Validators.required], confirmar: ['', Validators.required] },
{ validators: clavesCoinciden() }, // ← validador del GRUPO, ve todos los campos
);Validación asíncrona
Cuando validar requiere consultar el servidor (¿el email ya existe?), usas un validador
ASÍNCRONO: devuelve una Promise u Observable del error o null. Angular muestra el estado
pending mientras espera:
import { AsyncValidatorFn } from '@angular/forms';
import { map } from 'rxjs';
export function emailDisponible(api: ApiService): AsyncValidatorFn {
return (control) =>
api.existeEmail(control.value).pipe(
map((existe) => (existe ? { emailTomado: true } : null)),
);
}
// se pasa como TERCER argumento (validadores async):
email: ['', [Validators.required, Validators.email], [emailDisponible(this.api)]],Pruébalo: la lógica de validación en vivo
Un validador es una función pura —dado un valor, devuelve el error o null—. Esa es la parte
que puedes ejecutar y testear sin Angular. Aquí, varios validadores custom en acción:
Fíjate en combinar: recorre los validadores y devuelve el PRIMER error, o null si todos
pasan. Es exactamente lo que hace Angular con el array [Validators.required, min(1)]. La
lógica de validación es TypeScript puro y testeable —una de las razones por las que los
formularios reactivos escalan tan bien—.
¿Qué debe devolver un validador (`ValidatorFn`) cuando el valor es VÁLIDO?
Mini-reto
En el playground: 1) escribe un validador custom maxLongitud(n) (fábrica) que falle si el
texto supera n caracteres; 2) uno empiezaCon(prefijo) para un código de invitación;
3) combínalos con requerido para validar un campo codigo y prueba varios valores. Luego
piensa cómo escribirías la versión Angular: codigo: ['', [Validators.required, maxLongitud(8), empiezaCon('MB')]].
Qué sigue
Ya validas los datos. Pero validar es solo la mitad: el usuario necesita VER qué está mal, de
forma clara y accesible. La próxima lección cubre los estados del formulario
(touched/dirty), cómo mostrar mensajes de error en el momento justo, y cómo hacerlo
accesible para lectores de pantalla —un formulario que también se oye—.