A medida que las aplicaciones front crecen en complejidad, la necesidad de manejar estado de UI y actualizaciones reactivas de forma clara y eficiente se vuelve crítica. En este contexto, Angular signals emergen como el nuevo modelo de reactividad del framework: una forma explícita y predecible de declarar valores, derivaciones y efectos, reduciendo boilerplate y mejorando el rendimiento. Signals ofrecen un modelo mental simple: lees valores como funciones, declaras derivados y reaccionas a cambios sin sorpresas. Además, facilitan pruebas y mantenimiento gracias a dependencias explícitas y efectos controlados.
En este artículo veremos qué son los Angular signals, por qué pueden simplificar tu lógica de componentes, cómo integrarlos con patrones existentes y cuándo conviene usarlos frente a otras alternativas. También, cubriremos casos de uso frecuentes y mejores prácticas para incorporarlos en proyectos nuevos o en bases de código existentes.
¿Qué son los Angular signals?
Angular Signals es el sistema de reactividad fina de Angular (v16+). Permite modelar estado como unidades reactivas pequeñas (señales) que emiten cambios; así Angular actualiza solo lo que depende de ese dato, sin recorrer todo el árbol. Algunas de las piezas más clave son:
signal(value):
Un contenedor reactivo que guarda un valor y notifica cambios cuando lo actualizas. Se lee llamándolo como función (count()) y se escribe con .set, .update o .mutate.
Evita recomputaciones si el nuevo valor es igual al anterior (Object.is), y puedes personalizar la igualdad.
Para objetos/arrays, .mutate permite cambios in-place y emite notificación sin cambiar la referencia.
computed(fn):
Un valor derivado memoizado que se recalcula automáticamente cuando cambian las señales leídas en su función.
Es solo lectura y sincrónico: siempre refleja un estado consistente sincrónicamente con sus dependencias.
Úsalo para lógica pura (formateos, agregados, filtros locales); no está pensado para efectos o flujos asíncronos.
effect(fn):
Una reacción que ejecuta efectos secundarios (logs, DOM imperativo, llamadas a servicios) cuando cambian las señales leídas dentro de su función.
No produce un valor; solo actúa. Soporta limpieza mediante onCleanup para unsubscribe o cancelar tareas cuando cambie el estado o se destruya el efecto.
import { Component, signal, computed, effect } from '@angular/core';
@Component({
selector: 'app-counter', standalone: true,
template: “”
})
export class CounterComponent {
count = signal(0);
double = computed(() ⇒ this.count() * 2);
constructor() { effect(() ⇒ {
console.log('count:', this.count(), 'double:', this.double());
});
}
inc() {
this.count.update(v ⇒ v + 1);
}
}
Cuándo usar cada uno:
- signal para estado local de UI y datos mutables simples.
- computed para derivaciones puras (sin E/S ni asíncrono).
- effect para sincronizar el estado con el “mundo externoˮ (suscripciones, timers, DOM imperativo).
Notas prácticas:
- Las dependencias se rastrean por lectura: lo que llames dentro de computed/effect reactiva.
- Prefiere computed para evitar duplicar lógica y garantizar consistencia.
- En tareas asíncronas complejas o múltiples eventos, convive con RxJS: Signals para estado de presentación; RxJS para flujos.
Leer y escribir signals
- Leer: invoca la señal como función: count().
- En plantillas: {{ count() }} (no se desestructura).
- Leer dentro de computed/effect registra dependencia; fuera, no.
- Escribir:
- set(nuevoValor): asigna directamente.
- update(fn): calcula a partir del valor actual.
- mutate(fn): para objetos/arrays, permite cambios in-place y notifica. const count = signal(0);
count.set(1); // * 1 count.update(v ⇒ v + 2); // *3
const user = signal({ name: 'Ana', tags: [] as string[] });
user.mutate(u ⇒ u.tags.push('pro')); // notifica aunque la ref no cambie
Buenas prácticas:
- Prefiere update para operaciones dependientes del valor anterior.
- Usa mutate solo con estructuras mutables; si usas inmutabilidad, quédate con set.
- Evita leer signals fuera de contextos reactivos si no quieres reactividad implícita.
Problemas que resuelven los signals
- Renders innecesarios
El rastreo granular de dependencias evita repintar árboles completos: solo se invalida el trozo de UI que leyó la señal que cambió. Esto reduce jank, mejora FPS y hace más predecible el rendimiento en componentes grandes.
- Boilerplate en estado simple
Para estado local de UI, signal/computed/effect sustituyen plumbing típico
(suscripciones manuales, ngOnDestroy, changeDetectorRef ad-hoc). Menos código ceremonial, menos fugas y menos puntos de fallo.
- Estado duplicado
Derivar datos con computed() elimina copias y flags redundantes. Las derivaciones quedan siempre sincronizadas con las fuentes, evitando “desajustesˮ y reglas de actualización dispersas.
- Efectos desordenados
effect() encapsula side effects (logs, métricas, sincronización con APIs/DOM) y los dispara solo cuando cambian sus dependencias. Además, ofrece ciclo de vida y limpieza deterministas, reduciendo efectos zombis.
- Inputs verbosos
Con Signal Inputs, los cambios de entradas se consumen como señales, eliminando ngOnChanges y switches manuales. La reactividad es directa y expresiva, ideal para componentes altamente configurables.
- Tests inestables
Las dependencias quedan explícitas por lectura: lo que lees te reactiva. Esto simplifica mocks, evita sincronías frágiles y reduce flakiness al probar lógica de UI y derivaciones.
- Detección de cambios incierta
Signals hacen explícito cuándo y por qué algo se recalcula. Se minimizan sorpresas con zonas, estrategias de ChangeDetection y markForCheck dispersos.
- Mantenimiento y refactors costosos
Al centralizar la lógica en señales y derivaciones puras, los refactors impactan menos archivos y patrones, facilitando aislar responsabilidades y mover estado entre componentes sin romper contratos.
Cuándo usar Angular signals (casos comunes)
- Estado local de UI: toggles, modales, pestañas, pasos de un wizard.
- Derivados: totales, flags (canSubmit, hasErrors), filtros de listas.
- Inputs reactivos: componentes que recalculan mucho en función de props.
- Vistas muy interactivas: dashboards, tablas con filtros/ordenación local.
ejemplo (validación simple):
import { signal, computed } from '@angular/core';
const email = signal(''); const pass = signal('');
const canSubmit = computed(() ⇒ email().includes('@') && pass().length ?? 8
);
Buenas prácticas generales de Angular Signals
- Evitar duplicaciones de signal y utilizar computed
const first = signal('Ada');
const last = signal('Lovelace');
// Incorrecto
const fullIncorrecto = signal(${first()} ${last()});
// Correcto
const fullCorrecto = computed(() ⇒ ${first()} ${last()});
- Lógica en computed(), efectos en effect()
// Incorrecto
const a = signal(1), b = signal(2); let sum 0;
effect(() ⇒ { sum = a() + b(); }); // lógica dentro de effect
// Correcto
const sum = computed(() ⇒ a() + b());
effect(() ⇒ console.log(sum())); // efecto secundario
- No escribir dentro de computed()
// Incorrecto
const count = signal(0);
const doubledBad = computed(() ⇒ {
count.set(count() + 1); // escritura dentro de computed return count() * 2;
});
// Correcto
const doubled = computed(() ⇒ count() * 2);
- Usar update para mutación basada en valor previo
// Incorrecto const n = signal(0); n.set(n() + 1); // Correcto n.update(v ⇒ v + 1);
Cuándo usar Angular Signals frente a otras alternativas
- Estado local y síncrono de componente: usar Signals cuando el dato vive en el propio componente y no interviene I/O ni streaming.
- Valores derivados inmediatos: usar computed para totales, flags y filtros calculados al instante a partir de otras señales.
- Entradas que cambian con frecuencia (@Input): usar Signal Inputs cuando se requieren recalculos directos sin temporización ni orquestación.
- Interacciones de UI intensivas sin llamadas externas: usar Signals en toggles, modales, formularios ligeros y tablas con filtro/ordenación local.
- Necesidad de granularidad de render: usar Signals cuando importa actualizar solo la parte afectada de la vista, evitando renders innecesarios.
- Ciclo de vida simple: usar Signals cuando no se necesitan cancelaciones,
retry/backoff, debounce/throttle ni combinación de múltiples fuentes.
- Alcance acotado del estado: usar Signals cuando el estado no debe difundirse ampliamente por la aplicación.
- Prioridad de claridad y mantenimiento: usar Signals para reducir boilerplate y expresar relaciones de datos de forma explícita (estado → derivado → efecto).
Conclusión
Signals no son solo otra API: son una forma más clara y precisa de pensar el estado en Angular. Ayudan a construir interfaces reactivas con menos ruido, más control y resultados medibles en rendimiento y mantenimiento. El ecosistema seguirá evolucionando —y habrá espacio para streams y otras piezas—, pero
dominar signal → computed → effect hoy es un paso firme hacia un Angular más moderno, predecible y escalable. Conocerlo ahora nos coloca por delante: listos para adoptar lo que viene, sin complicar lo que ya funciona.
¿Quieres seguir aprendiendo todo sobre desarrollo web? ¡Síguenos en Redes Sociales y Canal de YouTube y no te pierdas nada!
