Primitivas reactivas de Angular: Uso de LinkedIn Signal y Computed
Clase 13 de 36 • Curso de Angular Avanzado
Resumen
La programación reactiva en Angular ha evolucionado significativamente con la introducción de primitivas como signals, computed y effects. Estas herramientas permiten crear aplicaciones más eficientes y con un código más declarativo. En esta ocasión, exploraremos una nueva primitiva llamada "writableComputed" (o linkedSignal), que resuelve algunas limitaciones comunes y mejora la experiencia de desarrollo manteniendo el rendimiento óptimo de nuestra aplicación.
¿Qué son las primitivas reactivas en Angular?
Angular ha desarrollado tres primitivas fundamentales para el manejo de estado reactivo:
- Signal: Permite declarar un estado de forma explícita y declarativa
- Computed: Crea un signal derivado que depende de otro signal
- Effect: Ejecuta funciones cuando detecta cambios en un signal
Aunque estas primitivas son poderosas, existen situaciones donde presentan limitaciones. Por ejemplo, una regla no escrita en Angular es evitar el uso excesivo de effects, ya que generalmente existen mejores alternativas. Aquí es donde entra en juego el linkedSignal (writableComputed).
¿Cómo funciona el writableComputed en un caso práctico?
Para entender mejor esta nueva primitiva, trabajaremos con un componente de detalle de producto que incluye un carrusel de imágenes. El objetivo es mejorar la implementación actual haciéndola más declarativa y eficiente.
Analicemos primero el código original:
// Implementación original
this.productService.getOne(id)
.subscribe(data => {
this.product.set(data);
if (data.images.length > 0) {
this.cover.set(data.images[0]);
}
});
En este código, cuando obtenemos los datos del producto, establecemos el producto en un signal y luego, si el producto tiene imágenes, establecemos la primera imagen como portada (cover).
Mejorando con computed
Podemos mejorar este código utilizando un computed, ya que la portada se deriva del producto:
// Usando computed
this.cover = computed(() => {
const product = this.product();
return product?.images?.[0] ?? '';
});
O de forma más explícita:
this.cover = computed(() => {
const product = this.product();
if (product && product.images.length > 0) {
return product.images[0];
}
return '';
});
Esta implementación es más declarativa, pero presenta un problema: no podemos modificar directamente un computed. Esto se convierte en un inconveniente cuando necesitamos cambiar la imagen de portada al hacer clic en una miniatura del carrusel:
// Esto no funcionaría con un computed
changeImage(image: string) {
this.cover.set(image); // Error: computed no tiene método set
}
La solución con writableComputed (linkedSignal)
Para resolver este problema, Angular introdujo el writableComputed (también conocido como linkedSignal), que combina las ventajas de computed con la capacidad de modificar su valor:
import { writableComputed } from '@angular/core';
// Usando writableComputed
this.cover = writableComputed(() => {
const product = this.product();
return product?.images?.[0] ?? '';
});
// Ahora podemos hacer esto sin problemas
changeImage(image: string) {
this.cover.set(image); // Funciona correctamente
}
Con esta implementación, obtenemos lo mejor de ambos mundos: un valor derivado que se actualiza automáticamente cuando cambia el producto, pero que también podemos modificar directamente cuando sea necesario.
¿Cómo acceder al valor anterior con writableComputed?
Una característica adicional del writableComputed es la capacidad de acceder al valor anterior. Esto se logra utilizando una sintaxis alternativa:
this.cover = writableComputed({
source: this.product, // El signal del que depende (sin paréntesis)
computation: (product, previousValue) => {
// Si no hay imágenes, mantener el valor anterior
if (!product || product.images.length === 0) {
return previousValue;
}
return product.images[0];
}
});
Esta forma de declaración nos da acceso al valor anterior (previousValue
), lo que puede ser útil en escenarios donde queremos mantener cierta información o realizar comparaciones con el estado previo.
Mejorando la estructura del código
Para mantener un código limpio y evitar confusiones, es recomendable seguir convenciones de nomenclatura. Una práctica común es añadir un prefijo $ a las variables que son signals:
// Usando convención de nomenclatura
this.$product = signal<Product | null>(null);
this.$cover = writableComputed(() => {
const product = this.$product();
return product?.images?.[0] ?? '';
});
// En el template
<div *ngIf="$product() && $cover()">
<img [src]="$cover()" alt="Product cover">
</div>
Esta convención hace que sea más fácil identificar qué variables son signals y evita conflictos de nombres.
El writableComputed (linkedSignal) es una herramienta poderosa que nos permite crear código más declarativo y eficiente en Angular, combinando las ventajas de los computed signals con la flexibilidad de poder modificar su valor directamente. ¿Has utilizado ya esta primitiva en tus proyectos? ¿Qué otras situaciones crees que podrían beneficiarse de su uso? Comparte tu experiencia en los comentarios.