8. Formularios: capturar y validar información¶
Del campo escrito por el usuario a un modelo listo para enviar.
Los seis pasos¶
flowchart LR
P1["1 · Definir controles<br/>nombre y correo con FormControl"] --> P2["2 · Asignar validadores<br/>required, email, longitud"]
P2 --> P3["3 · Vincular el HTML<br/>formGroup y formControlName"]
P3 --> P4["4 · Mostrar retroalimentación<br/>errores solo si el control fue tocado"]
P4 --> P5["5 · Validar al enviar<br/>si es inválido, marcar y detener"]
P5 --> P6["6 · Construir NewStudent<br/>getRawValue() → servicio"]
| Paso | Qué se hace |
|---|---|
| 1. Definir controles | Crear nombre y correo con FormControl, agrupados en un FormGroup |
| 2. Asignar validadores | required, email y reglas de longitud |
| 3. Vincular el HTML | formGroup y formControlName conectan vista y estado |
| 4. Mostrar retroalimentación | Errores visibles solo cuando el control fue tocado |
| 5. Validar al enviar | Si el formulario es inválido, marcar controles y detener |
6. Construir NewStudent |
Tomar getRawValue() tipado y delegarlo al servicio |
Estados que debemos comprender¶
| Estado | Significado |
|---|---|
valid |
Todos los controles cumplen sus reglas |
invalid |
Existe al menos un control con error |
touched |
El usuario ya interactuó con el control |
Formularios reactivos: ideas clave¶
FormControlrepresenta un campo y su estado.FormGroupreúne controles como un solo formulario.Validatorsdeclaran reglas reutilizables.- La validación del cliente mejora la experiencia, pero el backend debe volver a validar.
- Al enviar,
getRawValue()produce unNewStudentque el componente delega al servicio.
8.1 Formulario reactivo tipado: ejemplo completo¶
Importar → construir → validar → convertir en modelo → delegar al servicio.
src/app/models/student.model.ts
export type NewStudent = Omit<Student, 'id'>; // el backend generará el identificador
src/app/students/student-form/student-form.component.ts
import { Component, inject } from '@angular/core';
import {
ReactiveFormsModule, FormBuilder, Validators
} from '@angular/forms';
import { NewStudent } from '../../models/student.model';
import { StudentService } from '../../services/student.service';
@Component({
selector: 'app-student-form',
standalone: true,
imports: [ReactiveFormsModule],
templateUrl: './student-form.component.html'
})
export class StudentFormComponent {
private fb = inject(FormBuilder);
private service = inject(StudentService);
// Pasos 1 y 2: controles y validadores
form = this.fb.nonNullable.group({
name: ['', Validators.required],
email: ['', [
Validators.required, Validators.email
]],
active: [true]
});
submit(): void {
// Paso 5: validar al enviar
if (this.form.invalid) {
this.form.markAllAsTouched();
return;
}
// Paso 6: construir el modelo y delegar al servicio
const student: NewStudent =
this.form.getRawValue();
this.service.create(student).subscribe();
}
}
src/app/students/student-form/student-form.component.html
<!-- Paso 3: vincular el HTML -->
<form [formGroup]="form" (ngSubmit)="submit()">
<label>
Nombre
<input formControlName="name">
</label>
<!-- Paso 4: retroalimentación solo si el control fue tocado -->
@if (form.controls.name.touched && form.controls.name.hasError('required')) {
<small class="error">El nombre es obligatorio</small>
}
<label>
Correo
<input type="email" formControlName="email">
</label>
@if (form.controls.email.touched && form.controls.email.hasError('required')) {
<small class="error">El correo es obligatorio</small>
}
@if (form.controls.email.touched && form.controls.email.hasError('email')) {
<small class="error">El correo no tiene un formato válido</small>
}
<label>
<input type="checkbox" formControlName="active"> Activo
</label>
<button type="submit">Guardar</button>
</form>
Detalles que conviene notar:
nonNullablehace que cada control tenga un tipo preciso (string,boolean) en lugar destring | null; por esogetRawValue()encaja directamente enNewStudent.(ngSubmit)se dispara al enviar (botónsubmito Enter) y evita la recarga de la página.markAllAsTouched()hace visibles todos los errores aunque el usuario no haya pasado por cada campo.
Validadores más usados¶
| Validador | Error que produce |
|---|---|
Validators.required |
required |
Validators.email |
email |
Validators.minLength(n) / maxLength(n) |
minlength / maxlength |
Validators.min(n) / max(n) |
min / max |
Validators.pattern(regex) |
pattern |
¿Y después de guardar?¶
El ejemplo hace subscribe() sin manejar el resultado. Lo habitual es navegar al detalle si todo salió bien y mostrar un mensaje si falló; ese manejo es el tema de Excepciones:
this.service.create(student).subscribe({
next: created => this.router.navigate(['/students', created.id]),
error: () => this.message.set('No fue posible guardar el estudiante')
});
Validar en ambos lados
La validación del frontend mejora la experiencia. La validación del backend (@Valid sobre el DTO) garantiza la integridad: cualquiera puede enviar una petición a la API sin pasar por el formulario.
Idea clave
El componente valida y construye el modelo; el servicio se encarga de enviarlo a la API.
Siguiente: Excepciones.