Angular 21 de Cero a Experto / Servicios y DI
Servicios con signals: el store reactivo de Angular
Aquí se juntan las dos ideas más potentes que has aprendido: signals (reactividad de grano fino) y servicios (estado compartido). Un servicio que expone señales ES un store reactivo: la fuente de verdad de tu app, que cualquier componente lee y actualiza, y donde todo lo derivado se recalcula solo. En React esto pedía Zustand o Redux; en Angular es un patrón nativo con las herramientas que ya tienes. Hoy lo construyes y lo pruebas en vivo.
El tablero central del estadio
El patrón: privado dentro, readonly fuera
import { Injectable, signal, computed } from '@angular/core';
export interface Gasto { id: number; nombre: string; valor: number; categoria: string; }
@Injectable({ providedIn: 'root' })
export class BolsilloStore {
// 1. ESTADO privado: solo el store lo muta
private readonly _gastos = signal<Gasto[]>([]);
private readonly _presupuesto = signal(500000);
// 2. LECTURAS públicas de solo lectura
readonly gastos = this._gastos.asReadonly();
readonly presupuesto = this._presupuesto.asReadonly();
// 3. DERIVADOS con computed (se recalculan solos)
readonly total = computed(() => this._gastos().reduce((s, g) => s + g.valor, 0));
readonly saldo = computed(() => this._presupuesto() - this.total());
readonly cantidad = computed(() => this._gastos().length);
// 4. ACCIONES: la única forma de cambiar el estado
agregar(nombre: string, valor: number, categoria: string) {
this._gastos.update((gs) => [...gs, { id: Date.now(), nombre, valor, categoria }]);
}
eliminar(id: number) {
this._gastos.update((gs) => gs.filter((g) => g.id !== id));
}
fijarPresupuesto(v: number) {
this._presupuesto.set(v);
}
}Las cuatro capas —estado privado, lecturas readonly, derivados, acciones— son el molde de
todo store en Angular. Los componentes reciben una API limpia: leen señales, disparan
acciones, y nunca pueden corromper el estado con una mutación accidental. asReadonly()
devuelve una señal sin .set()/.update(): se puede leer pero no escribir desde fuera.
Un store, muchos consumidores
// El formulario AGREGA
@Component({ selector: 'app-form', template: `
<button (click)="store.agregar('Café', 4500, 'comida')">+ Café</button>
`})
export class FormComponent { protected store = inject(BolsilloStore); }
// El resumen LEE derivados
@Component({ selector: 'app-resumen', template: `
<p>Total: {{ store.total() }} — Saldo: {{ store.saldo() }}</p>
`})
export class ResumenComponent { protected store = inject(BolsilloStore); }Cuando el formulario llama store.agregar(...), el _gastos cambia, total y saldo se
recalculan, y el resumen se actualiza —aunque estén en ramas completamente distintas del
árbol de componentes—. Sin props, sin eventos que suban nivel a nivel. El store es el punto
de encuentro.
Antes (BehaviorSubject) vs Ahora (signals)
Constrúyelo y pruébalo en vivo
Un servicio Angular no corre en el sandbox, pero el PATRÓN del store —estado privado, lecturas, derivados, acciones— es TypeScript puro. Aquí está, funcionando, con tu mini-sistema de señales:
Observa que total y saldo nunca se calculan a mano: se derivan del estado y se
actualizan solos tras cada acción. Ese es exactamente el comportamiento que tendrías en
Angular real, donde además los templates se repintarían solos al cambiar las señales.
¿Por qué el estado (`_gastos`) es privado y se expone con `asReadonly()`?
Mini-reto
En el playground, amplía el store: 1) agrega una señal privada _categoria (filtro activo,
inicial "todos") con su acción filtrarPor(cat); 2) un computed gastosVisibles que
devuelva todos si es "todos" o los de esa categoría si no; 3) un computed totalVisible.
Dispara acciones y confirma que los derivados reaccionan. Es la base del reto del módulo.
Qué sigue
Tienes el patrón de store, el más importante para el estado en Angular. Antes de aplicarlo en el reto, dos temas que completan tu dominio de la DI: la jerarquía de inyectores (por qué a veces quieres una instancia por componente, no global) y los tokens de inyección (inyectar valores que no son clases). La próxima lección: la jerarquía.