Ir al contenido

Formularios Reactivos

Hasta ahora todos sus componentes solo leían datos. Un formulario es la primera vez que un componente captura algo que escribe el usuario, lo valida, y solo entonces lo envía. Angular necesita una forma de representar ese estado — lo que el usuario ha escrito, si es válido, si cambió — como algo que el código pueda inspeccionar.

Dos formas de construir formularios en Angular

Sección titulada «Dos formas de construir formularios en Angular»

Template-driven

El estado vive principalmente en el HTML, con ngModel. Es rápido para formularios muy simples, pero el tipo de los datos es difícil de rastrear desde el componente.

Reactive (el que usa este curso)

El estado vive en el componente, como un objeto TypeScript tipado. El HTML solo se conecta a ese objeto, no lo define.

FormGroup y FormControl: el formulario como objeto tipado

Sección titulada «FormGroup y FormControl: el formulario como objeto tipado»

Un FormGroup agrupa varios FormControl, uno por cada campo. El tipo genérico de FormGroup<{ ... }> declara la forma completa del formulario, igual que una interface declara la forma de un objeto:

artist-create.ts
artistaForm: FormGroup<{
nombre: FormControl<string>;
paisOrigen: FormControl<string>;
}> = new FormGroup({
nombre: new FormControl<string>('', {
nonNullable: true,
validators: [Validators.required],
}),
paisOrigen: new FormControl<string>('', {
nonNullable: true,
validators: [Validators.required],
}),
});

Cada FormControl recibe dos cosas: un valor inicial ('') y un objeto de opciones (nonNullable, validators).

Sin nonNullable: true, cada FormControl<string> sería en realidad FormControl<string | null>: Angular permite que un control quede en null (por ejemplo, al hacer .reset() sin un valor de reinicio explícito), y usted tendría que verificar null cada vez que lea el valor.

Validators: validez y tipo son cosas distintas

Sección titulada «Validators: validez y tipo son cosas distintas»

Validators.required marca el control como inválido mientras esté vacío, pero eso no cambia su tipo: sigue siendo string, nunca undefined ni null. Validez (“¿se puede enviar?”) y tipo (“¿qué forma tiene el valor?”) son dos preguntas distintas, y Validators solo responde la primera.

Validador Qué exige
Validators.required El control no puede quedar vacío.
Validators.minLength(n) Al menos n caracteres.
Validators.maxLength(n) A lo sumo n caracteres.
Validators.email El texto tiene forma de correo electrónico.
Validators.pattern(regex) El texto cumple una expresión regular.

Un control puede recibir varios validadores a la vez, como un arreglo: validators: [Validators.required, Validators.minLength(3)].

artist-create.html
<form [formGroup]="artistaForm" (ngSubmit)="guardar()">
<input formControlName="nombre" />
<input formControlName="paisOrigen" />
<button type="submit" [disabled]="artistaForm.invalid">
Crear artista
</button>
<button type="button" (click)="cancelar()">
Cancelar
</button>
</form>
  • [formGroup]="artistaForm" conecta todo el <form> con el objeto artistaForm del componente.
  • formControlName="nombre" conecta un <input> puntual con el FormControl correspondiente dentro del grupo.
  • (ngSubmit)="guardar()" dispara guardar() cuando el formulario se envía, ya sea con Enter o con un clic en un botón type="submit" dentro del <form>.
  • [disabled]="artistaForm.invalid" deshabilita el botón mientras algún validador falle.

Leer los datos: getRawValue() contra value()

Sección titulada «Leer los datos: getRawValue() contra value()»

artistaForm.value existe, pero TypeScript lo tipa como Partial del objeto del formulario (todas las propiedades opcionales), porque un control podría estar deshabilitado y quedar fuera de value. artistaForm.getRawValue(), en cambio, siempre devuelve todas las propiedades, con el tipo completo que declaró en el FormGroup.

artist-create.ts
guardar(): void {
if (this.artistaForm.invalid) return;
const datos = this.artistaForm.getRawValue();
// datos: { nombre: string; paisOrigen: string }
// tipado completo, sin Partial, gracias a nonNullable + getRawValue()
}

artistaForm.reset() devuelve cada control a su valor inicial — el que declaró al crear el FormControl ('' en los ejemplos de arriba). Gracias a nonNullable: true, ese valor inicial es siempre del tipo declarado, nunca null.

artist-create.ts
cancelar(): void {
this.artistaForm.reset();
}
1. ¿Por qué este curso usa formularios reactivos en vez de template-driven?
2. Sin nonNullable: true, ¿qué tipo tiene en realidad un FormControl<string>?
3. Un control tiene Validators.required y hoy está vacío. ¿Qué tipo tiene su valor en ese momento?
4. ¿Por qué el botón "Cancelar" dentro de un <form> necesita type="button"?
5. ¿Qué diferencia a artistaForm.getRawValue() de artistaForm.value?
6. ¿Cuándo se dispara (ngSubmit)?