Angular Signals

<< Formularios reactivos Integrar librerias >>

La API de Angular signals (señales) se introdujo en la versión 16 (como experimental) y se espera que esté completa en las próximas versiones de Angular. Por ahora tenemos disponible una parte de las posibilidades que esta API ofrecerá en un futuro próximo.

Una señal es un objeto que almacena un valor, y cada vez que ese valor se modifica, se notifica a aquellas partes del código que utilizan el valor de la señal (plantilla de un componente) o funciones programadas para ejecutarse automáticamente cuando el valor cambia.

Creación de señales

Para crear una señal llamaremos a la función signal (@angular/core) pasándole el valor inicial. Esto nos devolverá un objeto del tipo WritableSignal, lo que implica que su valor puede modificarse en cualquier momento.

Acceder al valor de la señal

Las señales funcionan también como getters del valor que almacenan. Esto quiere decir que para acceder a su valor, basta con añadir los paréntesis como si llamaramos a cualquier función. Esto también ocurre en la plantilla del componente.

Cambiar el valor de una señal

Para cambiar el valor de una señal tenemos 2 métodos. El primero es set, al que le pasaremos directamente el nuevo valor que almacenaremos.

También podemos utilizar el método update, que permite cambiar el valor mediante una función que recibe el valor actual. Lo que devolvemos en esta función sería el nuevo valor almacenado (similar al método map de los arrays).

Componentes basados en señales

En un futuro próximo, cuando la API de Angular signals esté finalizada, podremos crear componentes basados en señales o signal-based components . Esto permitirá crear componentes que en su plantilla solo reaccionará a cambios de valores que sean de tipo señal, y no a valores normales.

Esto permitirá, con unas modificaciones menores a la hora de trabajar con valores del componente en la plantilla (básicamente añadir paréntesis a dichos valores), mejorar notablemente el rendimiento y la eficiencia de las aplicaciones Angular. Esto se debe a que ya no tendremos la necesidad de usar la librería Zone.js que utiliza Angular de forma intensiva actualmente para vigilar los cambios en los valores que se sustituyen en las plantillas, y que consume bastantes recursos en grandes aplicaciones, sobre todo si no están muy optimizadas. Bastará con actualizar el valor concreto en la plantilla cuando la señal avise al componente de que ha habido un cambio.

Actualmente, lo más parecido es usar la estrategia de detección de cambios OnPush en los componentes, que limita la detección de cambios que hace Angular en el componente, lo que implica un menor coste computacional, a cambio de tener que indicarle a Angular manualmente que detecte cambios en situaciones concretas usando el método markForCheck() del servicio ChangeDetectorRef.

Señales calculadas: compute

Se pueden crear señales cuyos valores dependen de los valores de otras señales, y que cambian dinámicamente cuando los valores de las señales de las cual dependen cambian a su vez.

Para crear una señal dependiente de otras usamos la función compute. Esta función recibe una función, y lo que devuelva esta función será a su vez el valor que almacenará la señal que estamos creando. Si utilizamos valores de otras señales en esta función, cuando estas cambién, la función se ejecutará otra vez actualizando el valor de la señal dependiente.

Como ejemplo, vamos a gestionar el listado de productos y la búsqueda con señales. La señal calculada serán los productos filtrados por la cadena de búsqueda. Cada vez que cambie el array de productos (señal) o la cadena de búsqueda (señal), se recalcularán los productos filtrados. Observaremos los siguientes cambios sustanciales:

  • Ya no necesitaremos el pipe productsFilter. Los sustituimos por la señal calcaulada.
  • No se puede vincular un campo con ngModel a una señal de manera bidireccional (no en la versión 17 al menos). Por lo que usaremos el evento (ngModelChange) para actualizar el valor de la búsqueda.

Este tipo de señales calculadas a partir de otras son de solo lectura, no podemos cambiarles el valor directamente (no podemos llamar a set ni update), sino a través de las señales de las que dependen.

Además, el valor de este tipo de señales no se calcula al declararlas, sino la primera vez que se consulta el valor (lazy loading). Lo mismo ocurre si cambia el valor de una señal de la cual depende. En este caso se marca como modificada pero hasta que no se vuelve a consultar el valor de la señal calculada, no se recalcula su valor.

Operaciones cuando cambia una señal: effect

Se pueden crear funciones que se ejecuten automáticamente cuando cambie el valor de una señal. Según la documentación de Angular, posibles casos para el uso de estas operaciones serían:

  • Mostrar o almacenar datos para depuración de la aplicación.
  • Sincronizar datos en localStorage (o indexedDB).
  • Operaciones específicas del DOM que no se puedan hacer en la plantilla de Angular.
  • Actuar sobre un canvas (dibujar) o alguna librería gráfica que estemos usando.

Donde crear función effect

Las operaciones con effect se deben crear en el constructor si estamos trabajando en un componente, servicio o directiva. Esto es así porque deben crearse en el contexto del inyector de dependencias de Angular, ya que se registran como dependencias del componente, y son destruidas cuando el componente se destruye.

Modificar señales dentro de effect

Para evitar efectos colaterales como dependencias circulares que crearían un bucle infinito, por defecto no se pueden modificar señales dentro de una función effect. El motivo sería el siguiente:

  • La señal A se lee en una función effect que modifica la señal B
  • La señal B se lee en una función effect que modifica la señal A
  • Se crea un efecto ping-pong donde la ejecución del primer effect, activa el segundo, a su vez esto activa el primero, etc.

Si estamos seguros de querer modificar una señal dentro de una función effect y no va a tener efectos colaterales, podemos activar las escrituras pasándole un segundo paráemtro con la opción {allowSignalWrites: true}.

Señales compartidas en servicios (ejemplo login)

Un posible uso de las señales sería la de compartir un dato en un servicio al que acceden varios componentes, de tal manera que estos puedan acceder y reaccionar a dicho valor.

Una buena práctica de programación sería tener la señal en un atributo privado y compartirla con un getter que te devuelva una versión de solo lectura de esa misma señal. Así nos aseguramos que su valor solo se puede modificar desde los métodos del servicio.

Por ejemplo: Vamos a crear una señal booleana en el servicio AuthService que nos indique si el usuario está logueado o no. Al hacer login, pondremos el valor a true y al hacer logout, a false.

En el componente que contiene el menú superior, obtenemos el valor de la señal y lo utilizamos en la plantilla para mostrar u ocultar las opciones correspondientes. Si inyectamos el servicio como atributo privado, no podremos usarlo en la plantilla. Se puede hacer público, o mejor, crear una señal en el componente con el mismo valor que la del servicio.

<< Formularios reactivos Integrar librerias >>