Formularios con señales en Angular 21: cómo la API de señales revoluciona la gestión de formularios a nivel arquitectónico
Los formularios con señales abandonan la abstracción AbstractControl en favor de dos primitivas estrictamente tipadas:
FieldTree<T>— un objeto proxy que representa el árbol del formulario como señales. Cada nodo es unaSignal<FieldState<T>>, y el acceso al valor se realiza mediante una llamada().FieldState<T>— el estado de un campo específico: valor, estado (valido,tocado,sucio), errores y métodos de gestión (set,reset,markAsTouched).
Esto elimina la necesidad de sincronizar manualmente los datos de entrada (@Input) con el estado interno del formulario. En los formularios reactivos, se requería valueChanges más patchValue, lo que introducía riesgo de desincronización y complicaba la gestión del ciclo de vida. En los formularios con señales, el modelo se convierte directamente en la única fuente de verdad:
export interface LoginFormModel {
email: string;
password: string;
}
@Component({
imports: [Field],
template: `
<form>
Email: <input [field]="loginForm.email">
Password: <input [field]="loginForm.password">
</form>
`
})
export class LoginForm {
login = model.required<LoginFormModel>();
loginForm = form(this.login); // Vincula la señal del modelo con el árbol del formulario
}
Aquí, this.login es una WritableSignal<LoginFormModel>, y loginForm.email() devuelve una FieldState<string>. Al cambiar login.set({...}), todos los FieldState conectados se actualizan instantáneamente; y, viceversa, modificar el valor mediante loginForm.email().set(...) actualiza login().
Tipado sin pérdida: de AbstractControl a tipos precisos de campo
La principal ventaja es preservar la estructura de tipos en todos los niveles. En los formularios reactivos, get('nested.field') devuelve AbstractControl | null, perdiendo información sobre la estructura anidada. En los formularios con señales, la ruta path.additionalInformation.firstName tiene un tipo estricto de FieldState<string>, mientras que path.additionalInformation() es una FieldState<{ firstName: string; lastName: string; }>. Esto permite usar de forma segura effect, computed y otras primitivas de la API de señales sin necesidad de casting ni verificaciones de undefined.
Consideremos un ejemplo con el grupo additionalInformation:
effect(() => {
const info = this.loginForm.additionalInformation();
console.log(info.firstName().value()); // string — el compilador lo sabe exactamente
console.log(info.lastName().touched()); // boolean
});
Este tipado elimina errores clásicos: intentos de llamar a .value en null, errores al trabajar con FormGroup anidados y any implícito en cadenas como get().get().value.
Gestión de estados de los elementos: oculto, deshabilitado, solo lectura
Los formularios con señales ofrecen tres mecanismos integrados para controlar la visibilidad y la interactividad, cada uno con su propia semántica:
hidden(path, condition?)— elimina completamente el campo del DOM (mediante@if) y del árbol de validación. No afecta atouched/dirtyporque el elemento no existe en el formulario.disabled(path, condition?)— hace que el campo no sea editable y lo excluye de la validación y de los estadostouched/dirty.readonly(path, condition?)— bloquea la edición pero mantiene el campo en el ciclo de validación y permite rastreartouched/dirty.
Las condiciones se definen mediante una función que recibe el contexto { valueOf }. Por ejemplo:
form<{ email: string; password: string }>(
{ email: '', password: '' },
(path) => {
disabled(path.password, ({ valueOf }) => !valueOf(path.email));
}
);```
Esto equivale a `password.disabled = !email.value`, pero se ejecuta de forma reactiva y garantiza la consistencia.
## Debounce y validación: soluciones integradas en lugar de RxJS
Para controlar la frecuencia de las actualizaciones de datos, se implementa la función `debounce`, que acepta tanto un tiempo de espera en milisegundos como un `Debouncer` personalizado:
debounce(path, (
{ valueOf },
abortSignal
) => {
return new Promise<void>((resolve) => {
const timeout = setTimeout(() => resolve(), valueOf(path.email).length * 100);
abortSignal.addEventListener('abort', () => clearTimeout(timeout));
});
});```
La validación se divide en síncrona (required, minLength, pattern) y asíncrona (validateAsync, validateHttp). Una característica especial es el soporte para validateTree, que permite crear reglas entre campos. Por ejemplo, verificar la coincidencia de contraseñas:
validateTree(path, (ctx) => {
const { password, confirmPassword } = ctx.value();
if (password !== confirmPassword) {
return {
field: ctx.fieldTree.confirmPassword,
kind: 'passwordMismatch',
message: 'Las contraseñas no coinciden'
};
}
});```
Aquí, `ctx.fieldTree` proporciona acceso a todas las `FieldState` dentro del árbol, y `ctx.value()` entrega el valor actual de todo el formulario.
## Qué importa
- Los formularios con señales no sustituyen a los formularios reactivos; son una revisión arquitectónica bajo la API de señales: el modelo y el formulario ahora son una única fuente de verdad.
- El tipado se conserva en todos los niveles de anidamiento, eliminando `AbstractControl` y la necesidad de comprobaciones manuales de `instanceof`.
- `hidden`, `disabled` y `readonly` tienen una semántica clara y gestionan automáticamente la validación y los estados `touched/dirty`.
- `debounce` y `validateTree` están integrados en el núcleo, eliminando la dependencia de RxJS para escenarios básicos.
- Su estado experimental significa que la API puede cambiar; utilizarla en producción requiere comprender claramente los riesgos y contar con un plan de migración.
Los formularios con señales en Angular 21 representan un paso hacia una gestión de formularios más predecible, segura en cuanto a tipos y menos verbosa. No simplifican los casos sencillos, pero reducen radicalmente la complejidad en escenarios complejos: formularios anidados, campos condicionales, validación cruzada y sincronización dinámica. Para desarrolladores de nivel medio/senior, esto no es una herramienta de “inicio rápido”; es una primitiva arquitectónica que vale la pena estudiar a fondo, hasta el nivel de implementación de `FieldTree` y el comportamiento de `effect` dentro del contexto del formulario.
— Editorial Team
Aún no hay comentarios.