Volver al inicio

Angular 21 Signal Forms: Arquitectura y Tipado

El artículo detalla Signal Forms en Angular 21: Primitivas arquitectónicas FieldTree y FieldState, Ventajas de Tipado, Gestión de Estado de Elementos (Hidden/Disabled/Readonly), Debounce Integrado y Validación Entre Campos vía ValidateTree.

Angular 21: Signal Forms Sin Abstracciones
Advertisement 728x90

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 una Signal<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().

Google AdInline article slot

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.

Google AdInline article slot

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 a touched/dirty porque 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 estados touched/dirty.
  • readonly(path, condition?) — bloquea la edición pero mantiene el campo en el ciclo de validación y permite rastrear touched/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, (

Google AdInline article slot

{ 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

Advertisement 728x90

Leer después