Índice
Introducción a Angular Signals
Si llevas tiempo trabajando con Angular, sabes que la reactividad ha estado dominada por RxJS. No hay duda de que los Observables son poderosos, pero también pueden volverse complejos y difíciles de manejar en proyectos grandes.
Con Angular 18 y 19, el equipo de Angular nos trae Angular Signals, una nueva forma de manejar el estado en la UI de forma más intuitiva y eficiente. Pero esto plantea varias preguntas:
¿Cómo se compara con RxJS?
¿Es realmente más eficiente?
¿Cómo se integra con otros paradigmas de reactividad en otros frameworks?
En este artículo exploraremos qué son las Signals, sus ventajas, diferencias con RxJS y otros frameworks, buenas prácticas para su implementación y cómo pueden mejorar nuestras aplicaciones modernas en Angular.
¿Qué son las Angular Signals?
Las Signals son una nueva API en Angular que permite gestionar estado de manera reactiva y eficiente sin necesidad de suscripciones manuales. Piensa en ellas como contenedores de valores reactivos que notifican automáticamente a los consumidores cada vez que cambian.
Si has usado BehaviorSubject o ReplaySubject en RxJS para manejar estado, Signals simplifican este proceso al eliminar la necesidad de suscripciones y operadores complejos.
Principales ventajas de Angular Signals:
- Más eficientes: Angular rastrea exactamente qué partes de la UI dependen de qué valores y actualiza solo lo necesario.
- Menos código boilerplate: Se acabaron las suscripciones manuales y la necesidad de unsubscribe().
- Más predecibles: No hay que preocuparse por cuándo se dispara un .next() o si una suscripción quedó abierta.
- Mejor rendimiento: Signals reducen el trabajo innecesario en el ChangeDetection, lo que mejora la velocidad de renderizado.
- Compatibles con RxJS: Si ya usas Observables, puedes integrarlos fácilmente con Signals.
Comparación: Angular Signals vs. Otros Modelos de Reactividad
Cada framework moderno maneja la reactividad de manera diferente. ¿Cómo se comparan las Signals de Angular con otras soluciones populares?
Característica | Angular Signals | RxJS (Angular) | React (useState + useEffect) | Vue (reactive, ref, computed) | SolidJS (Reactivity Core) |
Complejidad | Baja | Alta | Media | Media | Baja |
Rendimiento | Alto | Medio | Medio | Alto | Muy alto |
Dependencias | Ninguna | RxJS | React Hooks | Vue Composition API | Reactivity Core nativo |
Flujos asíncronos | Limitado | Potente | Depende de useEffect | Integrado en Vue | Integrado |
Facilidad de uso | Alta | Baja | Media | Alta | Alta |
- Vs. React (useState + useEffect)
React usa useState para manejar estado local y useEffect para efectos secundarios. Signals eliminan la necesidad de dependencias y optimizan automáticamente el renderizado, algo que en React requiere memoization manual. - Vs. Vue (reactive, ref, computed)
Vue tiene un modelo de reactividad muy parecido al de Signals (ref, computed). Sin embargo, Signals tienen mejor integración con el renderizado de Angular y eliminan trabajo innecesario en el DOM. - Vs. SolidJS
SolidJS es probablemente lo más cercano a Signals en términos de eficiencia y optimización granular. Ambos sistemas eliminan re-renderizados innecesarios, pero SolidJS lo hace a nivel de compilación, mientras que Angular Signals opera en tiempo de ejecución. - Vs. RxJS
Si bien RxJS sigue siendo clave para flujos de datos complejos y asíncronos, Signals son más intuitivas y eficientes para el estado de UI, eliminando la necesidad de suscripciones y operadores extra.
Buenas Prácticas y Patrones con Angular Signals
Signals pueden hacer nuestro código más limpio y eficiente, pero también hay formas incorrectas de usarlas. Aquí algunos consejos clave:
1. Usa Signals para estado local, no para todo
Si bien Signals son potentes, no son una solución global para manejar estado en toda la aplicación. Usa Signals para estado reactivo en componentes y mantenlo separado de la lógica de negocio.
❌ Mal uso (convertir todo el estado de la app en Signals)
export class StoreService {
users = signal<User[]>([]);
isLoading = signal(false);
}
✅ Buen uso (usar Signals solo donde realmente se necesiten)
}export class StoreService {
users = signal<User[]>([]);
isLoading = signal(false);
}
2. No abuses de las Signals anidadas
Si necesitas almacenar objetos dentro de una Signal, evita mutarlos directamente.
❌ Mutar directamente el estado (rompe la reactividad)
userSignal().name = "Nuevo Nombre"; // NO HACER ESTO
✅ Actualizar el estado correctamente
userSignal.set({ ...userSignal(), name: "Nuevo Nombre" });
3. Usa computed() para cálculos derivados
Si necesitas un valor derivado, usa computed() en lugar de recalcularlo manualmente.
import { computed, signal } from '@angular/core';
const price = signal(100);
const tax = signal(0.16);
const total = computed(() => price() * (1 + tax()));
console.log(total()); // 116
4. Usa efectos (effect) con cuidado
effect() es útil para ejecutar código en respuesta a cambios de Signals, pero evita usarlo para lógica de negocio pesada.
import { effect } from '@angular/core';
effect(() => {
console.log("El total ha cambiado:", total());
});
5. Combina Signals con RxJS cuando sea necesario
Si ya usas RxJS en tu aplicación, no intentes reemplazarlo por completo. Usa Signals para estado de UI y RxJS para flujos de datos asíncronos.
import { toSignal } from '@angular/core/rxjs-interop';
import { of } from 'rxjs';
const observable$ = of(42);
const signalValue = toSignal(observable$);
console.log(signalValue()); // 42
Ejemplos Prácticos y Casos de Uso Avanzado
Hasta ahora hemos hablado de qué son las Signals y cómo pueden hacer nuestro código más limpio y eficiente. Pero, ¿cómo se usan en situaciones reales? Aquí algunos ejemplos prácticos avanzados.
1. Uso de Signals en un Store Global
En aplicaciones grandes, necesitamos manejar estado compartido entre múltiples componentes. Con Signals, podemos construir un store global sin necesidad de NgRx o servicios con BehaviorSubject.
import { Injectable, signal } from '@angular/core';
@Injectable({ providedIn: 'root' })
export class CounterStore {
count = signal(0);
increment() {
this.count.set(this.count() + 1);
}
decrement() {
this.count.set(this.count() - 1);
}
}
Uso en un componente:
import { Component, inject } from '@angular/core';
import { CounterStore } from './counter.store';
@Component({
selector: 'app-counter',
template: `
Contador: {{ counterStore.count() }}
`, }) export class CounterComponent { counterStore = inject(CounterStore); }
- No requiere suscripciones ni ngOnDestroy.
- Optimización automática del renderizado.
- Menos código comparado con NgRx o un servicio basado en RxJS.
2. Uso de Signals en Formularios Reactivos
Uno de los retos en Angular es manejar formularios reactivos de manera eficiente. Con Signals, podemos mejorar la reactividad sin depender de observables.
Ejemplo: Sincronización de un formulario con Signals
import { Component } from ‘@angular/core’;
import { FormControl, FormGroup } from ‘@angular/forms’;
import { signal, computed } from ‘@angular/core’;
@Component({
selector: ‘app-login’,
template: `
<form [formGroup]=»form«>
<input formControlName=»email» placeholder=»Email»>
<input formControlName=»password» type=»password» placeholder=»Contraseña»>
</form>
<p>Formulario válido: {{ isValid() }}</p>
`,
})
export class LoginComponent {
form = new FormGroup({
email: new FormControl(»),
password: new FormControl(»),
});
isValid = computed(() => this.form.valid);
✅ Ventaja: computed() mantiene la reactividad sin necesidad de suscripciones a valueChanges.
3. Uso de Signals en Angular Router
Ahora podemos usar Signals para reaccionar a cambios de parámetros en la URL sin necesidad de suscribirse a ActivatedRoute.params.
Ejemplo: Obtención de un parámetro de la URL
import { Component, inject } from '@angular/core';
import { signal } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-product',
template: `Producto ID: {{ productId() }}
`,
})
export class ProductComponent {
route = inject(ActivatedRoute);
productId = signal(this.route.snapshot.paramMap.get('id') || '');
}
✅ Ventajas:
- Eliminamos la necesidad de this.route.params.subscribe().
- No es necesario limpiar suscripciones en ngOnDestroy.
El Futuro de Angular Signals y la Dirección de Angular: ¿Signals es el futuro de la reactividad en Angular?
Angular Signals es una de las mayores revoluciones en la historia de Angular. Pero, ¿hacia dónde va Angular con esta nueva API?
Aunque RxJS seguirá siendo clave en Angular, es evidente que Signals está tomando un papel central en el manejo del estado. Algunos puntos clave a considerar:
NgRx podría evolucionar hacia Signals
NgRx ha sido la solución estándar para manejar estado global en Angular, pero su complejidad ha sido una barrera de entrada. En el futuro podríamos ver una versión más ligera de NgRx basada en Signals.
Eliminación de Change Detection basado en Zone.js
Angular aún usa Zone.js para detectar cambios en la UI. Signals podría allanar el camino para un Angular sin Zone.js, mejorando el rendimiento de las aplicaciones.
Mayor integración con SSR e Islands Architecture
Con Signals y la hidratación incremental en Angular 19, parece que Angular se está moviendo hacia una arquitectura más eficiente para aplicaciones server-side y con renderizado progresivo.
En resumen
Angular Signals es una de las mejoras más interesantes en Angular en los últimos años. Facilitan la gestión del estado, optimizan el renderizado y reducen la complejidad de la reactividad en los componentes.
Sin embargo, Signals no reemplazan a RxJS, sino que lo complementan. Mientras Signals optimizan el estado de UI, RxJS sigue siendo la mejor opción para manejar flujos de datos asíncronos, eventos y combinaciones de múltiples fuentes de datos.
Si estás en Angular 18 o 19, este es el momento ideal para experimentar con Signals. Y si alguna vez has pasado horas peleando con pipe(), mergeMap() y unsubscribe()… Signals te van a parecer un respiro de aire fresco.