La evolución hacia los "signals" en Angular
Contexto y motivación
La motivación principal detrás de la introducción de los "signals" en Angular radica en la búsqueda de una mayor granularidad y eficiencia en la detección de cambios, así como en la simplificación de la gestión del estado. Históricamente, en Angular, las propiedades de los componentes se actualizaban y su detección de cambios dependía del ciclo de vida de la zona. Esto significaba que cualquier operación asíncrona dentro de la zona podía desencadenar un ciclo de detección de cambios completo, afectando a la aplicación en su conjunto. Con los "signals", el objetivo es pasar a un modelo en el que solo las partes de la interfaz de usuario que realmente dependen de un valor de "signal" específico se actualicen cuando ese valor cambie. Esto no solo promete mejoras significativas en el rendimiento al reducir las operaciones innecesarias, sino que también hace que el flujo de datos sea mucho más explícito y fácil de seguir. Es un cambio fundamental que alinea Angular con tendencias modernas en otros frameworks y librerías, como Solid.js o Preact con Signals, que ya han demostrado los beneficios de este enfoque. Personalmente, considero que esta dirección es un gran acierto, ya que aborda algunas de las complejidades inherentes al modelo de detección de cambios actual.¿Qué son los "signals"? Breve repaso
Antes de sumergirnos en los "signal inputs", es crucial tener una comprensión básica de qué son los "signals" en Angular. Un "signal" es un envoltorio reactivo alrededor de un valor. Puede ser cualquier tipo de dato: un número, una cadena, un objeto, un array. Lo especial de un "signal" es que te permite leer su valor de manera síncrona y, lo que es más importante, notifica a cualquier "interesado" (cualquier código que haya leído su valor) cuando su valor cambia. Esto crea un grafo de dependencias reactivas donde los cambios fluyen de manera predecible y eficiente. Existen tres tipos principales de "signals": 1. **`signal()`:** Crea un "signal" mutable. Puedes actualizar su valor directamente usando el método `set()` o `update()`. ```typescript import { signal } from '@angular/core'; const contador = signal(0); console.log(contador()); // 0 contador.set(1); console.log(contador()); // 1 contador.update(valorActual => valorActual + 1); console.log(contador()); // 2 ``` 2. **`computed()`:** Crea un "signal" de solo lectura cuyo valor se calcula a partir de otros "signals". Es perezoso, lo que significa que su valor solo se recalcula cuando se lee y cuando las dependencias de "signal" de las que depende cambian. ```typescript import { signal, computed } from '@angular/core'; const precio = signal(10); const cantidad = signal(2); const total = computed(() => precio() * cantidad()); // Depende de precio y cantidad console.log(total()); // 20 cantidad.set(3); console.log(total()); // 30 (recalculado) ``` 3. **`effect()`:** Registra una función que se ejecuta cada vez que uno o más "signals" que lee cambian. Los efectos siempre se ejecutan al menos una vez y son útiles para sincronizar el estado de "signals" con el DOM, hacer logging, o interactuar con APIs externas que no son reactivas. ```typescript import { signal, effect } from '@angular/core'; const nombreUsuario = signal('Juan'); effect(() => { console.log(`El nombre de usuario ha cambiado a: ${nombreUsuario()}`); }); nombreUsuario.set('Pedro'); // Output: El nombre de usuario ha cambiado a: Pedro ``` Esta infraestructura de "signals" es la base sobre la que se construyen los "signal inputs", llevándola a la interacción entre componentes. Para más detalles sobre la implementación general de los signals, puedes consultar la documentación oficial de Angular sobre Signals.La novedad: "signal inputs" en Angular
Con las versiones recientes de Angular (especialmente a partir de la v17.1 con `model()` y la v17.3 con `input()`), la reactividad basada en "signals" se ha extendido al mecanismo de entrada de datos de los componentes. Los "signal inputs" son una forma moderna y declarativa de definir propiedades de entrada para los componentes, aprovechando directamente el poder de los "signals". Esto no solo simplifica la manera en que los componentes reciben y reaccionan a los datos de sus padres, sino que también elimina la necesidad de `ngOnChanges` en muchos escenarios, lo cual es una ganancia considerable en simplicidad y claridad de código. Al adoptar `signal inputs`, estamos dando un paso importante hacia componentes completamente reactivos y, potencialmente, hacia una arquitectura sin `Zone.js` en el futuro. Es un cambio que, para mí, mejora drásticamente la ergonomía del desarrollador.Declaración y uso básico
Declarar un "signal input" es sorprendentemente sencillo y se asemeja mucho a la forma en que se declaraban los "signals" regulares. En lugar de usar el decorador `@Input()`, ahora usamos la función `input()` o `input.required()` dentro de la clase del componente. Aquí hay un ejemplo de cómo definir un componente con un "signal input" simple: ```typescript // src/app/componentes/tarjeta-usuario/tarjeta-usuario.component.ts import { Component, input } from '@angular/core'; import { NgIf, NgClass } from '@angular/common'; // Importaciones para las plantillas @Component({ selector: 'app-tarjeta-usuario', standalone: true, // Habilitar componentes independientes imports: [NgIf, NgClass], // Módulos necesarios template: `{{ titulo() }}
Nombre: {{ nombre() }}
Rol: Administrador