En esta página
Formularios reactivos con validación
Formularios reactivos
Los formularios reactivos (Reactive Forms) definen la estructura del formulario en TypeScript, dando control total sobre validación, estado y transformaciones.
Configurar Reactive Forms
Importa ReactiveFormsModule en el componente:
import { ReactiveFormsModule } from '@angular/forms';
@Component({
imports: [ReactiveFormsModule],
// ...
})FormBuilder
FormBuilder es un servicio que simplifica la creación de controles:
import { inject } from '@angular/core';
import { FormBuilder, Validators } from '@angular/forms';
const fb = inject(FormBuilder);
const formulario = fb.nonNullable.group({
nombre: ['', Validators.required],
edad: [0, [Validators.required, Validators.min(18)]],
});Tipos de controles
| Tipo | Uso | Ejemplo |
|---|---|---|
FormControl |
Un solo valor | Input de texto, checkbox |
FormGroup |
Grupo de controles | Formulario completo |
FormArray |
Lista dinámica | Agregar/quitar campos |
FormArray ejemplo
readonly formulario = this.fb.nonNullable.group({
título: [''],
etiquetas: this.fb.array([
this.fb.control('angular'),
this.fb.control('typescript'),
]),
});
agregarEtiqueta(valor: string): void {
const etiquetas = this.formulario.controls.etiquetas;
etiquetas.push(this.fb.control(valor));
}Validadores integrados
Angular incluye validadores comunes:
import { Validators } from '@angular/forms';
fb.control('', [
Validators.required, // Campo obligatorio
Validators.minLength(3), // Longitud mínima
Validators.maxLength(50), // Longitud máxima
Validators.email, // Formato email
Validators.min(0), // Valor mínimo
Validators.max(100), // Valor máximo
Validators.pattern(/^\d+$/), // Expresión regular
]);Validadores personalizados
Crea funciones que reciben un AbstractControl y devuelven errores o null:
function emailCorporativo(control: AbstractControl): ValidationErrors | null {
const email: string = control.value;
if (!email) return null;
return email.endsWith('@empresa.com')
? null
: { emailNoCorporativo: true };
}Mostrar errores en el template
@if (formulario.controls.email.touched
&& formulario.controls.email.errors; as errores) {
@if (errores['required']) {
<span class="error">Campo obligatorio</span>
} @else if (errores['email']) {
<span class="error">Formato de email invalido</span>
}
}Estado del formulario
| Propiedad | Descripcion |
|---|---|
.valid |
Todos los controles son validos |
.invalid |
Al menos un control es invalido |
.touched |
El usuario interactuo con el control |
.dirty |
El valor fue modificado |
.pristine |
El valor no ha sido modificado |
Práctica
- Crea un formulario de contacto: Usa
FormBuilderpara crear un formulario con campos nombre, email y mensaje. AgregaValidators.requiredyValidators.emaildonde corresponda. - Implementa un validador personalizado: Crea una funcion validadora que verifique que un campo de texto no contenga palabras prohibidas. Asociala a un control del formulario.
- Muestra errores condicionalmente: Usa
@ifcon.touchedy.errorspara mostrar mensajes de error especificos debajo de cada campo solo despues de que el usuario interactue con ellos.
En la siguiente leccion aprenderemos a conectar con APIs usando HttpClient.
nonNullable
Usa fb.nonNullable.group() para que los controles no acepten null. Así getRawValue() devuelve tipos estrictos sin null, mejorando la inferencia de TypeScript.
No mezcles enfoques
No combines Template-driven forms (ngModel) con Reactive forms (formGroup) en el mismo formulario. Elige uno u otro. Angular 21 recomienda Reactive forms para formularios complejos.
import {
Component, inject, signal,
ChangeDetectionStrategy,
} from '@angular/core';
import {
ReactiveFormsModule, FormBuilder,
Validators, AbstractControl, ValidationErrors,
} from '@angular/forms';
// Validador personalizado
function passwordSegura(control: AbstractControl): ValidationErrors | null {
const valor: string = control.value;
if (!valor) return null;
const tieneNumero = /\d/.test(valor);
const tieneMayuscula = /[A-Z]/.test(valor);
const tieneLongitud = valor.length >= 8;
if (tieneNumero && tieneMayuscula && tieneLongitud) return null;
return {
passwordDebil: {
requiereNumero: !tieneNumero,
requiereMayuscula: !tieneMayuscula,
requiereLongitud: !tieneLongitud,
},
};
}
@Component({
selector: 'app-registro',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [ReactiveFormsModule],
templateUrl: './registro.html',
})
export class Registro {
private readonly fb = inject(FormBuilder);
readonly enviado = signal(false);
readonly formulario = this.fb.nonNullable.group({
nombre: ['', [Validators.required, Validators.minLength(3)]],
email: ['', [Validators.required, Validators.email]],
password: ['', [Validators.required, passwordSegura]],
aceptaTerminos: [false, Validators.requiredTrue],
});
enviar(): void {
if (this.formulario.invalid) {
this.formulario.markAllAsTouched();
return;
}
const datos = this.formulario.getRawValue();
console.log('Registro:', datos);
this.enviado.set(true);
}
}
Inicia sesión para guardar tu progreso