Angular Signals para flujos complejos: Ejemplos prácticos


Angular Signals es una de las características más recientes del ecosistema de Angular, diseñada para mejorar la reactividad y la eficiencia en la gestión de estado. En aplicaciones con flujos complejos, la correcta implementación de signals permite una arquitectura más predecible, reduciendo renders innecesarios y mejorando el rendimiento.

En este artículo exploraremos cómo usar Angular Signals en escenarios avanzados, incluyendo su aplicación en gestión de estado, comunicación entre componentes y sincronización de datos asíncronos.

¿Qué son los Signals en Angular?

Los Signals en Angular proporcionan una forma declarativa y eficiente de manejar datos reactivos. A diferencia de RxJS, los signals permiten una actualización automática y determinista del estado sin necesidad de suscripciones explícitas.

Ventajas de Signals en Angular

  • Actualización automática: Reactualizan valores cuando su dependencia cambia.
  • Menor complejidad: No requieren suscripciones manuales.
  • Rendimiento optimizado: Reducen renders innecesarios.
  • Compatibles con computación derivada: Se pueden encadenar para formar estructuras reactivas más complejas.

Ejemplo básico de un Signal en Angular:

import { signal } from '@angular/core';

export class ContadorComponent {
  contador = signal(0);

  incrementar() {
    this.contador.set(this.contador() + 1);
  }
}

1. Gestión de Estado Global con Signals

En aplicaciones con flujos complejos, es fundamental manejar el estado de manera centralizada y eficiente. Los Signals permiten crear una solución sencilla para compartir estado entre múltiples componentes.

Implementación de un Store con Signals

Creamos un servicio global para gestionar el estado de un carrito de compras.

import { Injectable, signal } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class CarritoService {
  private productos = signal<{ id: number, nombre: string, cantidad: number }[]>([]);

  agregarProducto(producto: { id: number, nombre: string }) {
    this.productos.set([...this.productos(), { ...producto, cantidad: 1 }]);
  }

  obtenerCarrito() {
    return this.productos;
  }
}

En un componente, podemos suscribirnos al estado de esta forma:

import { Component, inject } from '@angular/core';
import { CarritoService } from './carrito.service';

@Component({
  selector: 'app-carrito',
  template: `
    <ul>
      <li *ngFor="let item of carrito()">{{ item.nombre }} ({{ item.cantidad }})</li>
    </ul>
  `
})
export class CarritoComponent {
  carrito = inject(CarritoService).obtenerCarrito();
}

Beneficio: No es necesario usar RxJS ni BehaviorSubject, reduciendo la complejidad.

2. Comunicación entre Componentes con Signals

En aplicaciones modulares, los Signals facilitan la comunicación eficiente entre componentes sin necesidad de @Input() y @Output().

Ejemplo: Estado Compartido entre Componentes Hermanos

1️⃣ Servicio Compartido

import { Injectable, signal } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class EstadoService {
  contador = signal(0);
}

2️⃣ Componente que Modifica el Estado

import { Component, inject } from '@angular/core';
import { EstadoService } from './estado.service';

@Component({
  selector: 'app-incrementador',
  template: `<button (click)="incrementar()">Incrementar</button>`
})
export class IncrementadorComponent {
  estadoService = inject(EstadoService);
  incrementar() {
    this.estadoService.contador.set(this.estadoService.contador() + 1);
  }
}

3️⃣ Componente que Muestra el Estado

import { Component, inject } from '@angular/core';
import { EstadoService } from './estado.service';

@Component({
  selector: 'app-contador',
  template: `<p>Contador: {{ contador() }}</p>`
})
export class ContadorComponent {
  contador = inject(EstadoService).contador;
}

Beneficio: Los componentes comparten estado sin necesidad de event bindings manuales.

3. Manejo de Datos Asíncronos con Signals

Los Signals pueden manejar llamadas HTTP y actualizar automáticamente la UI.

Ejemplo: Llamada a una API con Signals

import { Component, inject, signal } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Component({
  selector: 'app-usuarios',
  template: `
    <ul>
      <li *ngFor="let usuario of usuarios()">{{ usuario.name }}</li>
    </ul>
  `
})
export class UsuariosComponent {
  private http = inject(HttpClient);
  usuarios = signal<{ name: string }[]>([]);

  constructor() {
    this.http.get<{ name: string }[]>('https://jsonplaceholder.typicode.com/users')
      .subscribe(data => this.usuarios.set(data));
  }
}

Beneficio: Se eliminan suscripciones manuales y async pipe, mejorando la simplicidad.

4. Optimización del Renderizado con Signals

Signals minimizan renders innecesarios. En comparación con @Input(), los Signals permiten actualizar solo la parte del estado que cambia.

Ejemplo: Renderización Optimizada

import { Component, Input, signal } from '@angular/core';

@Component({
  selector: 'app-producto',
  template: `<p>{{ producto().nombre }} - {{ producto().precio }}</p>`
})
export class ProductoComponent {
  @Input() producto = signal({ nombre: '', precio: 0 });
}

Beneficio: Solo se renderiza la parte del estado que cambia, mejorando la eficiencia.

Conclusión

Los Signals en Angular proporcionan un mecanismo moderno y eficiente para manejar flujos de datos complejos. Su integración con la arquitectura de Angular mejora la legibilidad, el rendimiento y la mantenibilidad del código.


Ver también

comments powered by Disqus