blense.
InicioFrontendProgramaciónIANoticiasGuíasCheatsheetsAcerca de

Tecnología descifrada, siempre.

Una lectura curada sobre IA, producto y la cultura detrás del código. Sin ruido, sin hype.

blense.

Tecnología con foco. Historias sobre lo que la innovación realmente cambia.

Secciones

FrontendInteligencia artificialProgramación y desarrolloNoticias tech

Blense

Acerca deRSSPrivacidadTérminosCookies
© 2026 Blense · blense.fun
Frontend

Guía de Angular Signals que Transformará tu Programación Reactiva

Finalmente, un primitivo reactivo que hace la gestión de estado intuitiva y performante sin la complejidad de RxJS

Gpor Genildo Souza13 dic11 min de lectura
Guía de Angular Signals que Transformará tu Programación Reactiva

¡Hola, developers! 👋

¿Recuerdas la última vez que debugueaste una cadena compleja de RxJS con múltiples suscripciones, async pipes por todos lados, y ese memory leak que no podías rastrear? ¿O cuando tuviste que explicarle a un desarrollador junior por qué necesita hacer unsubscribe de los observables??

Hoy, quiero compartir Angular Signals - un nuevo primitivo reactivo que cambia fundamentalmente cómo manejamos el estado en aplicaciones Angular. Al final de este artículo, entenderás cómo aprovechar signals para tener programación reactiva más limpia y performante sin la sobrecarga tradicional de RxJS.

¿Qué son los Angular Signals?

Piensa en signals como "variables inteligentes" que automáticamente rastrean cuándo son leídas y notifican cuando cambian. Son como un rastreador GPS para tus datos - siempre sabiendo quién está observando y actualizando eficientemente solo lo que necesita cambiar.

Arsenal del dev de élite.
Micro-frontends Angular con Module Federation: Guía Completa15 minAngular Renacido: olvida lo que sabías sobre el framework5 minDeja de arriesgar tu frontend: la arquitectura definitiva de Angular 2218 minAngular 22: Reemplace ControlValueAccessor con Signal Forms14 min

Angular Signals resuelven el problema fundamental de la reactividad granular: saber exactamente qué cambió y actualizar solo las partes afectadas de tu UI, sin gestión manual de suscripciones o preocupaciones con change detection.

¿Cuándo Deberías Usar Angular Signals?

Buenos casos de uso:

  • Gestión de estado de componentes que necesita actualizaciones reactivas

  • Valores computados derivados de otras fuentes reactivas

  • Estado y lógica de validación de formularios

  • Estado compartido entre componentes sin servicios

  • Actualizaciones de UI críticas para performance con change detection mínimo

Cuándo NO usar Signals:

  • Peticiones HTTP y operaciones asíncronas (continúa con Observables)

  • Streams de eventos complejos que necesitan operators como debounce, throttle

  • Integración con codebases pesadas en RxJS (usa interop con cuidado)

Signals: Tu Primera Implementación

Construyamos un ejemplo práctico: un contador de productos con cálculo de precio en tiempo real que demuestra los conceptos principales de signals.

Paso 1: Creando Tu Primer Signal

TYPESCRIPT
import { Component, signal } from '@angular/core';@Component({  selector: 'app-producto',  template: `    <div>      <h2>Producto: {{ nombreProducto() }}</h2>      <p>Cantidad: {{ cantidad() }}</p>      <button (click)="incrementar()">Agregar al Carrito</button>    </div>  `})export class ProductoComponent {  // Creando signals escribibles  nombreProducto = signal('Libro Angular');  cantidad = signal(0);    incrementar() {    // Actualizando valor del signal    this.cantidad.set(this.cantidad() + 1);  }}

Este código crea dos signals - nota cómo los llamamos como funciones en el template. Los signals son funciones que retornan su valor actual cuando son llamadas.

Paso 2: Trabajando con Computed Signals

TYPESCRIPT
import { Component, signal, computed } from '@angular/core';@Component({  selector: 'app-producto',  template: `    <div>      <p>Cantidad: {{ cantidad() }}</p>      <p>Precio unitario: ${{ precioUnitario() }}</p>      <p>Total: ${{ precioTotal() }}</p>      <button (click)="incrementar()">Agregar Item</button>    </div>  `})export class ProductoComponent {  cantidad = signal(1);  precioUnitario = signal(29.99);    // Computed signal actualiza automáticamente cuando las dependencias cambian  precioTotal = computed(() => {    return this.cantidad() * this.precioUnitario();  });    incrementar() {    this.cantidad.update(c => c + 1);  }}

Los computed signals recalculan automáticamente cuando sus dependencias cambian. Sin suscripciones, sin actualizaciones manuales - simplemente funciona.

Paso 3: Signal Effects para Side Effects

TYPESCRIPT
import { Component, signal, computed, effect } from '@angular/core';@Component({  selector: 'app-producto'})export class ProductoComponent {  cantidad = signal(0);  inventario = signal(10);    constructor() {    // Effect ejecuta cuando los signals que lee cambian    effect(() => {      if (this.cantidad() > this.inventario()) {        console.log('¡Advertencia: La cantidad excede el inventario!');        this.mostrarAdvertenciaInventario = true;      }    });  }    agregarAlCarrito() {    if (this.cantidad() < this.inventario()) {      this.cantidad.update(c => c + 1);    }  }}

Los effects rastrean automáticamente las dependencias de signals y se re-ejecutan cuando esos signals cambian - perfecto para logging, analytics, o manipulaciones del DOM.

Un Ejemplo Más Complejo: Carrito de Compras con Filtros

Construyamos algo más realista - un carrito de compras con filtrado y cálculos en tiempo real:

TYPESCRIPT
import { Component, signal, computed } from '@angular/core';interface Producto {  id: number;  nombre: string;  precio: number;  categoria: string;  enStock: boolean;}@Component({  selector: 'app-carrito-compras',  template: `    <div class="carrito">      <input         placeholder="Buscar productos..."         (input)="terminoBusqueda.set($event.target.value)"      />            <select (change)="categoriaSeleccionada.set($event.target.value)">        <option value="todas">Todas las Categorías</option>        <option *ngFor="let cat of categorias()" [value]="cat">          {{ cat }}        </option>      </select>            <div class="productos">        <div *ngFor="let producto of productosFiltrados()">          <h3>{{ producto.nombre }}</h3>          <p>${{ producto.precio }}</p>          <button             (click)="agregarAlCarrito(producto)"            [disabled]="!producto.enStock"          >            Agregar al Carrito          </button>        </div>      </div>            <div class="resumen">        <p>Items en el carrito: {{ itemsCarrito().length }}</p>        <p>Total: ${{ totalCarrito() }}</p>        <p>Con impuesto (10%): ${{ totalConImpuesto() }}</p>      </div>    </div>  `})export class CarritoComprasComponent {  // Signals de estado  productos = signal<Producto[]>([    { id: 1, nombre: 'Portátil', precio: 999, categoria: 'Electrónica', enStock: true },    { id: 2, nombre: 'Ratón', precio: 29, categoria: 'Electrónica', enStock: true },    { id: 3, nombre: 'Escritorio', precio: 299, categoria: 'Muebles', enStock: false },    { id: 4, nombre: 'Silla', precio: 199, categoria: 'Muebles', enStock: true }  ]);    itemsCarrito = signal<Producto[]>([]);  terminoBusqueda = signal('');  categoriaSeleccionada = signal('todas');    // Computed signals para estado derivado  categorias = computed(() => {    const cats = new Set(this.productos().map(p => p.categoria));    return Array.from(cats);  });    productosFiltrados = computed(() => {    const termino = this.terminoBusqueda().toLowerCase();    const categoria = this.categoriaSeleccionada();        return this.productos().filter(producto => {      const coincideBusqueda = producto.nombre.toLowerCase().includes(termino);      const coincideCategoria = categoria === 'todas' || producto.categoria === categoria;      return coincideBusqueda && coincideCategoria;    });  });    totalCarrito = computed(() => {    return this.itemsCarrito().reduce((suma, item) => suma + item.precio, 0);  });    totalConImpuesto = computed(() => {    return this.totalCarrito() * 1.1; // 10% impuesto  });    agregarAlCarrito(producto: Producto) {    this.itemsCarrito.update(items => [...items, producto]);  }}

Este ejemplo muestra cómo los signals manejan elegantemente relaciones reactivas complejas sin gestión manual de suscripciones.

Patrón Avanzado: Validación de Formulario Basada en Signals

Construyamos algo aún más sofisticado - un formulario reactivo con validación en tiempo real usando signals:

TYPESCRIPT
import { Component, signal, computed, effect } from '@angular/core';interface ErroresFormulario {  email?: string;  contrasena?: string;  confirmarContrasena?: string;}@Component({  selector: 'app-formulario-registro',  template: `    <form (submit)="enviarFormulario($event)">      <div>        <input           type="email"           placeholder="Email"          [value]="email()"          (input)="email.set($event.target.value)"          [class.error]="errores().email"        />        <span class="msg-error">{{ errores().email }}</span>      </div>            <div>        <input           type="password"           placeholder="Contraseña"          [value]="contrasena()"          (input)="contrasena.set($event.target.value)"          [class.error]="errores().contrasena"        />        <span class="msg-error">{{ errores().contrasena }}</span>      </div>            <div>        <input           type="password"           placeholder="Confirmar Contraseña"          [value]="confirmarContrasena()"          (input)="confirmarContrasena.set($event.target.value)"          [class.error]="errores().confirmarContrasena"        />        <span class="msg-error">{{ errores().confirmarContrasena }}</span>      </div>            <button [disabled]="!formularioValido()">        Registrarse      </button>            <div class="medidor-fuerza">        Fuerza de la Contraseña: {{ fuerzaContrasena() }}      </div>    </form>  `})export class FormularioRegistroComponent {  // Signals de campos del formulario  email = signal('');  contrasena = signal('');  confirmarContrasena = signal('');  camposTocados = signal<Set<string>>(new Set());    // Reglas de validación como computed signals  errores = computed<ErroresFormulario>(() => {    const errores: ErroresFormulario = {};    const tocados = this.camposTocados();        // Validación de email    if (tocados.has('email')) {      const valorEmail = this.email();      if (!valorEmail) {        errores.email = 'El email es obligatorio';      } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(valorEmail)) {        errores.email = 'Formato de email inválido';      }    }        // Validación de contraseña    if (tocados.has('contrasena')) {      const pwd = this.contrasena();      if (!pwd) {        errores.contrasena = 'La contraseña es obligatoria';      } else if (pwd.length < 8) {        errores.contrasena = 'La contraseña debe tener al menos 8 caracteres';      }    }        // Validación de confirmación de contraseña    if (tocados.has('confirmarContrasena')) {      if (this.contrasena() !== this.confirmarContrasena()) {        errores.confirmarContrasena = 'Las contraseñas no coinciden';      }    }        return errores;  });    fuerzaContrasena = computed(() => {    const pwd = this.contrasena();    if (pwd.length < 6) return 'Débil';    if (pwd.length < 10) return 'Media';    if (/[A-Z]/.test(pwd) && /[0-9]/.test(pwd) && /[^A-Za-z0-9]/.test(pwd)) {      return 'Fuerte';    }    return 'Media';  });    formularioValido = computed(() => {    return this.email() &&            this.contrasena() &&            this.confirmarContrasena() &&           Object.keys(this.errores()).length === 0;  });    constructor() {    // Auto-guardar borrador en localStorage    effect(() => {      const borrador = {        email: this.email(),        timestamp: Date.now()      };      localStorage.setItem('borradorRegistro', JSON.stringify(borrador));    });  }    marcarComoTocado(campo: string) {    this.camposTocados.update(campos => {      campos.add(campo);      return new Set(campos);    });  }    enviarFormulario(event: Event) {    event.preventDefault();    if (this.formularioValido()) {      console.log('Formulario enviado:', {        email: this.email(),        contrasena: this.contrasena()      });    }  }}

Esto demuestra cómo los signals pueden reemplazar librerías de formularios complejas con lógica de validación simple y reactiva.

Angular Signals con TypeScript

Para usuarios de TypeScript, aquí está cómo hacer tus implementaciones de signal type-safe:

JAVASCRIPT
// tipos.tsinterface Usuario {  id: number;  nombre: string;  email: string;  rol: 'admin' | 'usuario' | 'invitado';}interface EstadoApp {  usuarioActual: Usuario | null;  estaAutenticado: boolean;  permisos: string[];}// signal-store.service.tsimport { Injectable, signal, computed, Signal } from '@angular/core';@Injectable({ providedIn: 'root' })export class SignalStore {  // Signals escribibles con tipos explícitos  private _usuarioActual = signal<Usuario | null>(null);  private _estaCargando = signal<boolean>(false);    // Computed signals de solo lectura  readonly usuarioActual: Signal<Usuario | null> = this._usuarioActual.asReadonly();  readonly estaAutenticado = computed<boolean>(() => !!this._usuarioActual());    readonly permisos = computed<string[]>(() => {    const usuario = this._usuarioActual();    if (!usuario) return [];        switch(usuario.rol) {      case 'admin': return ['leer', 'escribir', 'eliminar', 'admin'];      case 'usuario': return ['leer', 'escribir'];      case 'invitado': return ['leer'];    }  });    // Métodos de actualización type-safe  login(usuario: Usuario): void {    this._usuarioActual.set(usuario);  }    actualizarUsuario(actualizaciones: Partial<Usuario>): void {    this._usuarioActual.update(actual =>       actual ? { ...actual, ...actualizaciones } : null    );  }}// Uso con TypeScript@Component({  selector: 'app-perfil',  template: `    <div *ngIf="store.usuarioActual() as usuario">      <h2>{{ usuario.nombre }}</h2>      <p>Rol: {{ usuario.rol }}</p>      <ul>        <li *ngFor="let perm of store.permisos()">          {{ perm }}        </li>      </ul>    </div>  `})export class PerfilComponent {  constructor(public store: SignalStore) {}}

Patrones Avanzados y Mejores Prácticas

1. Patrón de Composición de Signals

Crea signals de orden superior que combinan múltiples fuentes de signals:

JAVASCRIPT
// Componer múltiples signals en un único estado reactivofunction crearListaPaginada<T>(items: Signal<T[]>, tamañoPagina: number) {  const paginaActual = signal(0);    const totalPaginas = computed(() =>     Math.ceil(items().length / tamañoPagina)  );    const itemsPaginados = computed(() => {    const inicio = paginaActual() * tamañoPagina;    return items().slice(inicio, inicio + tamañoPagina);  });    return {    items: itemsPaginados,    paginaActual: paginaActual.asReadonly(),    totalPaginas,    siguientePagina: () => paginaActual.update(p => Math.min(p + 1, totalPaginas() - 1)),    paginaAnterior: () => paginaActual.update(p => Math.max(p - 1, 0))  };}

2. Patrón de Memoización de Signals

Optimiza cálculos costosos con signals memoizados:

JAVASCRIPT
// Memoizar operaciones costosasfunction crearSignalMemoizado<T, R>(  fuente: Signal<T>,  calcular: (valor: T) => R,  igualdad?: (a: R, b: R) => boolean) {  let ultimaEntrada: T | undefined;  let ultimaSalida: R | undefined;    return computed(() => {    const actual = fuente();    if (ultimaEntrada === actual && ultimaSalida !== undefined) {      return ultimaSalida;    }    ultimaEntrada = actual;    ultimaSalida = calcular(actual);    return ultimaSalida;  }, { equal: igualdad });}

3. Patrón de Debouncing de Signals

Implementa signals con debounce para búsqueda y manejo de inputs:

JAVASCRIPT
// Signal con debounce para inputs de búsquedafunction crearSignalConDebounce<T>(valorInicial: T, retraso: number) {  const inmediato = signal(valorInicial);  const conDebounce = signal(valorInicial);  let timeoutId: any;    const definir = (valor: T) => {    inmediato.set(valor);    clearTimeout(timeoutId);    timeoutId = setTimeout(() => {      conDebounce.set(valor);    }, retraso);  };    return {    inmediato: inmediato.asReadonly(),    conDebounce: conDebounce.asReadonly(),    definir  };}// Usoconst busqueda = crearSignalConDebounce('', 300);// busqueda.inmediato() - valor instantáneo// busqueda.conDebounce() - valor con debounce para llamadas API

4. Patrón de Máquina de Estados con Signals

Construye máquinas de estados robustas con signals:

JAVASCRIPT
// Máquina de estados usando signalsfunction crearMaquinaDeEstados<T extends string>(  estadoInicial: T,  transiciones: Record<T, T[]>) {  const estadoActual = signal(estadoInicial);    const puedeTransicionarA = computed(() => {    return transiciones[estadoActual()] || [];  });    const transicionarA = (nuevoEstado: T) => {    if (puedeTransicionarA().includes(nuevoEstado)) {      estadoActual.set(nuevoEstado);      return true;    }    return false;  };    return {    estado: estadoActual.asReadonly(),    puedeTransicionarA,    transicionarA  };}

Errores Comunes a Evitar

1. Mutar Objetos Dentro de Signals

JAVASCRIPT
// ❌ No hagas esto - mutar objeto no dispara actualizacionesconst usuario = signal({ nombre: 'Juan', edad: 30 });usuario().nombre = 'María'; // ¡Esto no disparará change detection!// ✅ Haz esto en su lugar - crea nueva referencia de objetousuario.update(u => ({ ...u, nombre: 'María' }));// Ousuario.set({ ...usuario(), nombre: 'María' });

2. Crear Signals Dentro de Computed

JAVASCRIPT
// ❌ Ejemplo problemático - crea nuevo signal en cada cálculoconst computedMalo = computed(() => {  const signalTemp = signal(0); // ¡No crees signals aquí!  return signalTemp() + otroSignal();});// ✅ Solución - crea signals fuera de computedconst signalTemp = signal(0);const computedBueno = computed(() => {  return signalTemp() + otroSignal();});

3. Olvidar Llamar Funciones Signal

TSX
// ❌ Evita este patrón - olvidando paréntesis@Component({  template: `<div>{{ contador }}</div>` // ¡No se actualizará!})export class ComponenteMalo {  contador = signal(0);}// ✅ Enfoque preferido - siempre llama signals como funciones@Component({  template: `<div>{{ contador() }}</div>` // Propiamente reactivo})export class ComponenteBueno {  contador = signal(0);}

Cuándo NO Usar Signals

No uses signals cuando:

  • Trabajando con peticiones HTTP - Los Observables manejan mejor las operaciones asíncronas

  • Necesites operators de stream complejos (debounce, throttle, retry) - RxJS es más poderoso

  • Integrando con APIs existentes basadas en Observable - sobrecarga de conversión innecesaria

JAVASCRIPT
// ❌ Exageración para escenarios simplesconst resultadoHttp = signal<Datos | null>(null);this.http.get('/api/datos').subscribe(datos => {  resultadoHttp.set(datos); // Conversión innecesaria});// ✅ Solución simple es mejordatos$ = this.http.get('/api/datos');// Usa async pipe en el template

Signals vs RxJS Observables

Los Signals son geniales para:

  • Gestión de estado síncrono

  • Valores computados simples

  • Actualizaciones de UI críticas para performance

  • Reducir código boilerplate

Considera Observables cuando necesites:

  • Operaciones asíncronas → Peticiones HTTP, streams WebSocket

  • Operators complejos → debounceTime, switchMap, retry

  • Streams de eventos → fromEvent, interval, timer

Conclusión

Angular Signals son una herramienta poderosa que puede simplificar dramáticamente la gestión de estado en tus aplicaciones. Traen reactividad granular, rastreo automático de dependencias, y mejor rendimiento a tus componentes Angular.

Puntos clave:

  • Los signals son funciones que mantienen y rastrean valores reactivos

  • Los computed signals derivan automáticamente estado de otros signals

  • Los effects manejan side effects con rastreo automático de dependencias

  • Los signals eliminan la gestión manual de suscripciones y memory leaks

La próxima vez que vayas a usar un Subject o BehaviorSubject para estado de componente, recuerda los signals. Tu código será más limpio, más performante, y más fácil de entender.

¿Ya has empezado a usar signals en tus proyectos Angular? ¿Qué patrones has descubierto? ¡Comparte tus experiencias en los comentarios!


Si esto te ayudó a mejorar tus habilidades en Angular, ¡sígueme para más patrones y mejores prácticas modernas de Angular! 🚀

Recursos

  • Documentación Oficial de Angular Signals

  • Especificación de la Propuesta TC39 Signals

  • Angular RFC: Sub-RFC 4: Componentes Basados en Signal

Sigue explorando
¿Quieres más contenido así?

Mira otros artículos en la misma línea y mantén el ritmo.

Ver más artículos
#JavaScript#Angular

Arsenal del dev de élite.

Guía de estrategias de carga HTML que acelerará sus tiempos de carga
Frontend

Guía de estrategias de carga HTML que acelerará sus tiempos de carga

Descubre cómo optimizar la carga de scripts con defer y async para eliminar bloqueos de renderizado y mejorar la velocidad de tu sitio web.

Genildo Souza · 24 jun · 9 min
Tailwind CSS v4: Cómo acelerar tus builds 100x con la nueva configuración CSS-first
Frontend

Tailwind CSS v4: Cómo acelerar tus builds 100x con la nueva configuración CSS-first

Descubre cómo Tailwind CSS v4 revoluciona el desarrollo frontend con builds ultrarrápidos y una nueva configuración CSS-first sin complicaciones.

Genildo Souza · 20 jun · 16 min
Zustand + TanStack Query: La dupla poderosa para la gestión de estado en React
Frontend

Zustand + TanStack Query: La dupla poderosa para la gestión de estado en React

Descubre por qué Zustand y TanStack Query son la combinación ideal para separar el estado de cliente del servidor y simplificar tu código en React.

Genildo Souza · 18 jun · 11 min
En este artículo
  • ¿Qué son los Angular Signals?
  • ¿Cuándo Deberías Usar Angular Signals?
  • Signals: Tu Primera Implementación
  • Paso 1: Creando Tu Primer Signal
  • Paso 2: Trabajando con Computed Signals
  • Paso 3: Signal Effects para Side Effects
  • Un Ejemplo Más Complejo: Carrito de Compras con Filtros
  • Patrón Avanzado: Validación de Formulario Basada en Signals
  • Angular Signals con TypeScript
  • Patrones Avanzados y Mejores Prácticas
  • 1. Patrón de Composición de Signals
  • 2. Patrón de Memoización de Signals
  • 3. Patrón de Debouncing de Signals
  • 4. Patrón de Máquina de Estados con Signals
  • Errores Comunes a Evitar
  • 1. Mutar Objetos Dentro de Signals
  • 2. Crear Signals Dentro de Computed
  • 3. Olvidar Llamar Funciones Signal
  • Cuándo NO Usar Signals
  • Signals vs RxJS Observables
  • Conclusión
  • Recursos