Angular Signals es el cambio más importante en el modelo de reactividad de Angular desde su creación. Introducido en Angular 16 y estabilizado en Angular 17, Signals representa una forma fundamentalmente diferente de pensar sobre el estado y la reactividad en nuestras aplicaciones.
¿Qué es un Signal?
Un Signal es un envoltorio reactivo alrededor de un valor. Cuando ese valor cambia, cualquier cosa que dependa de él se actualiza automáticamente.
import { signal, computed, effect } from '@angular/core';
const count = signal(0);
console.log(count()); // 0
count.set(5);
count.update(prev => prev + 1); // 6
¿Por qué Signals y no solo RxJS?
| Característica | RxJS Observables | Signals |
|---|---|---|
| Suscripción manual | Sí | No (automático) |
| Valor síncrono | No | Sí |
| Curva aprendizaje | Alta | Baja |
| Flujos complejos | Excelente | Limitado |
| Change Detection | Zone.js | Granular |
Los tres pilares: signal, computed y effect
1. signal() — Estado mutable
const name = signal('German');
const user = signal<User>({ name: 'German', role: 'admin' });
const items = signal<string[]>(['Angular', 'TypeScript']);
user.update(current => ({ ...current, role: 'editor' }));
items.update(current => [...current, 'Signals']);
2. computed() — Valores derivados
const firstName = signal('German');
const lastName = signal('Cordellat');
const fullName = computed(() => `${firstName()} ${lastName()}`);
console.log(fullName()); // "German Cordellat"
firstName.set('Juan');
console.log(fullName()); // "Juan Cordellat"
Ejemplo real: Carrito de compras
@Component({...})
export class CartComponent {
items = signal<CartItem[]>([]);
totalItems = computed(() =>
this.items().reduce((sum, item) => sum + item.quantity, 0)
);
subtotal = computed(() =>
this.items().reduce((sum, item) => sum + (item.price * item.quantity), 0)
);
tax = computed(() => this.subtotal() * 0.21);
total = computed(() => this.subtotal() + this.tax());
isEmpty = computed(() => this.items().length === 0);
}
3. effect() — Efectos secundarios
export class ThemeComponent {
theme = signal<'light' | 'dark'>('light');
constructor() {
effect(() => {
document.documentElement.setAttribute('data-theme', this.theme());
localStorage.setItem('preferred-theme', this.theme());
});
}
}
Model Inputs: Signals bidireccionales
@Component({
selector: 'app-rating',
template: `
@for (star of stars(); track $index) {
<button (click)="value.set($index + 1)">
{{ $index < value() ? '★' : '☆' }}
</button>
}
`
})
export class RatingComponent {
value = model(0);
max = input(5);
stars = computed(() => Array(this.max()));
}
Conclusión
Signals no reemplaza a RxJS, sino que lo complementa. Usa Signals para estado local y sincronía, RxJS para flujos asíncronos complejos, y el paquete rxjs-interop como puente entre ambos mundos.