Volver al blog
Imagen de portada del artículo: Angular Signals: La Revolución de la Reactividad sin RxJS

Angular Signals: La Revolución de la Reactividad sin RxJS

Guía completa de Angular Signals: desde los fundamentos hasta patrones avanzados con computed, effect y la nueva reactividad granular que transforma la forma en que gestionamos estado.

15 min
German

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ísticaRxJS ObservablesSignals
Suscripción manualNo (automático)
Valor síncronoNo
Curva aprendizajeAltaBaja
Flujos complejosExcelenteLimitado
Change DetectionZone.jsGranular

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.