Angular 21 de Cero a Experto / Formularios
Formularios dinámicos con FormArray
Hasta ahora tus formularios tenían campos fijos. Pero muchos casos reales necesitan un número
VARIABLE de campos: agregar varios gastos de golpe, una lista de invitados que crece, ítems de
una factura. Para eso está FormArray: un contenedor de controles que puedes agrandar y
encoger en tiempo de ejecución. Hoy construyes formularios que crecen con el usuario.
La lista que crece
Crear y manipular un FormArray
import { Component, inject } from '@angular/core';
import { FormBuilder, FormArray, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
@Component({
selector: 'app-varios-gastos',
imports: [ReactiveFormsModule],
template: `...`,
})
export class VariosGastosComponent {
private fb = inject(FormBuilder);
form = this.fb.group({
gastos: this.fb.array([this.crearFila()]), // arranca con una fila
});
// getter tipado para acceder al array cómodamente
get gastos(): FormArray {
return this.form.controls.gastos;
}
private crearFila(): FormGroup {
return this.fb.group({
nombre: ['', Validators.required],
valor: [0, [Validators.required, Validators.min(1)]],
});
}
agregar() { this.gastos.push(this.crearFila()); } // + una fila
quitar(i: number) { this.gastos.removeAt(i); } // − la fila i
}Las operaciones clave del FormArray: push(control) agrega al final, removeAt(i) elimina
por índice, .at(i) accede a uno, .length cuenta, .clear() vacía. Cada fila es un
FormGroup completo (con sus propios campos y validadores), así que validas cada gasto
individualmente y el conjunto a la vez.
Renderizarlo en el template
Un FormArray se recorre con @for, usando formGroupName con el ÍNDICE para conectar cada
fila:
template: `
<form [formGroup]="form">
<div formArrayName="gastos">
@for (fila of gastos.controls; track $index) {
<div [formGroupName]="$index">
<input formControlName="nombre" placeholder="Nombre" />
<input formControlName="valor" type="number" placeholder="Monto" />
<button type="button" (click)="quitar($index)">Quitar</button>
</div>
}
</div>
<button type="button" (click)="agregar()">+ Agregar gasto</button>
<button [disabled]="form.invalid">Guardar todos</button>
</form>
`,El formArrayName="gastos" conecta el bloque con el array; dentro, @for sobre
gastos.controls pinta una fila por control, y [formGroupName]="$index" enlaza cada fila
con su FormGroup por posición. Al pulsar "+", agregar() hace push y aparece una fila
nueva; al pulsar "Quitar", removeAt la elimina. El template reacciona solo.
La lógica de un FormArray, en vivo
El comportamiento de un FormArray —una colección que crece, se encoge, y cuya validez
depende de todas sus filas— es lógica pura. Aquí, un mini-FormArray funcionando:
Observa cómo valid depende de TODAS las filas: una sola fila inválida vuelve inválido el
array entero —igual que en Angular—. push y removeAt cambian el tamaño en runtime, y
total es un derivado de las filas actuales. Ese es el modelo mental del FormArray.
¿Cuándo necesitas un `FormArray` en lugar de un `FormGroup`?
Mini-reto
En el playground, amplía el ArrayDeGastos: 1) un método clear() que vacíe todas las filas;
2) un getter errores que devuelva un array con { indice, error } de cada fila inválida;
3) un método insertAt(i, fila). Prueba agregar, quitar e insertar. Luego escribe cómo se
vería el @for de Angular que renderiza este array con formGroupName.
Qué sigue
Cierras la teoría de formularios: reactivos tipados, validación en tres capas, errores accesibles y arrays dinámicos. Es hora del reto del módulo: construir el formulario de "nuevo gasto" completo —validado, accesible y con la lógica probada en vivo—. La puerta de datos de tu app, hecha con estándar profesional.