Angular 21 de Cero a Experto / Estado a gran escala
NgRx SignalStore: stores estructurados con señales
Cuando tu app tiene muchos stores, escribir a mano el patrón "estado privado + lecturas +
derivados + acciones" una y otra vez se vuelve repetitivo. NgRx SignalStore empaqueta ese
patrón en una API declarativa y componible: withState, withComputed, withMethods. Es la
recomendación moderna de NgRx —construida sobre señales, no sobre Observables—. Hoy la conoces
y construyes un mini-SignalStore para entender su elegancia por dentro.
Bloques que se ensamblan
Un SignalStore básico
import { signalStore, withState, withComputed, withMethods, patchState } from '@ngrx/signals';
import { computed } from '@angular/core';
interface Gasto { id: number; nombre: string; valor: number; }
interface BolsilloState { gastos: Gasto[]; presupuesto: number; }
export const BolsilloStore = signalStore(
{ providedIn: 'root' }, // se inyecta como cualquier servicio
// 1. el estado inicial (se expone como señales de solo lectura)
withState<BolsilloState>({ gastos: [], presupuesto: 500000 }),
// 2. derivados (computed) a partir del estado
withComputed((store) => ({
total: computed(() => store.gastos().reduce((s, g) => s + g.valor, 0)),
saldo: computed(() => store.presupuesto() - store.gastos().reduce((s, g) => s + g.valor, 0)),
})),
// 3. métodos (acciones) que actualizan el estado con patchState
withMethods((store) => ({
agregar(nombre: string, valor: number) {
patchState(store, {
gastos: [...store.gastos(), { id: Date.now(), nombre, valor }],
});
},
eliminar(id: number) {
patchState(store, { gastos: store.gastos().filter((g) => g.id !== id) });
},
})),
);Reconoces las cuatro capas de tu BolsilloStore del módulo 3 —estado, lecturas, derivados,
acciones— pero ahora estructuradas con bloques estándar. El componente lo consume igual:
inject(BolsilloStore), y lee store.total(), llama store.agregar(...). patchState es la
forma controlada de actualizar (actualiza solo las claves que le pasas, inmutablemente).
withEntities: CRUD de colecciones gratis
El patrón más repetitivo es manejar una colección de entidades (agregar, actualizar, eliminar
por id). withEntities lo automatiza:
import { withEntities, setAllEntities, addEntity, removeEntity } from '@ngrx/signals/entities';
export const GastosStore = signalStore(
{ providedIn: 'root' },
withEntities<Gasto>(), // te da entities() (la lista) y utilidades CRUD
withMethods((store) => ({
cargar(gastos: Gasto[]) { patchState(store, setAllEntities(gastos)); },
agregar(g: Gasto) { patchState(store, addEntity(g)); },
quitar(id: number) { patchState(store, removeEntity(id)); },
})),
);
// store.entities() → la lista; el CRUD por id ya viene resueltoEn vez de escribir el filter/map/spread de cada operación, withEntities te da un
almacén de entidades con helpers probados. Menos código repetido, menos bugs.
Antes (NgRx clásico) vs Ahora (SignalStore)
Constrúyelo: un mini-SignalStore en vivo
La magia de signalStore es la COMPOSICIÓN: cada with* recibe el store parcial y le añade
propiedades. Aquí, una versión mínima que compone estado + derivados + métodos, en vivo:
Observa la idea central: signalStore es un reduce que pasa el store parcial por cada bloque
with*, y cada bloque le AÑADE propiedades (señales de estado, derivados, métodos). Esa
composición es lo que hace a SignalStore extensible: withEntities es solo otro bloque que
añade una colección y sus helpers. Ahora, cuando leas un SignalStore real, verás la estructura
que acabas de construir.
¿Qué ventaja principal ofrece NgRx SignalStore frente al NgRx clásico (Store/Actions/Reducers)?
Mini-reto
En el playground, amplía tu mini-SignalStore: 1) añade al withState un campo filtro (string);
2) un withComputed gastosVisibles que filtre según filtro; 3) un método filtrarPor(cat)
que use patchState. Prueba agregar y filtrar. Luego escribe cómo se vería con el
signalStore real de @ngrx/signals.
Qué sigue
Tienes la herramienta para stores estructurados. Pero una app grande no es un store gigante: es muchas FEATURES, cada una con su estado, componentes y servicios, bien delimitadas. La próxima lección es la arquitectura por features —cómo organizar los archivos de una app que vive años—, conectando con lo que viste en React (SC-06 M10).