NgRx Signal Store vs NgRx Clásico en 2026: ¿Cuál Elegir?

Comparación detallada entre NgRx Signal Store y NgRx clásico para la gestión de estado en Angular 2026. Conoce las diferencias arquitectónicas, ejemplos de código y consejos para elegir la solución correcta.

NgRx Signal Store vs NgRx Clásico en 2026: ¿Cuál Elegir?

NgRx Signal Store representa un cambio fundamental en la gestión de estado de Angular, reemplazando el patrón tradicional inspirado en Redux (actions-reducers-selectors) por un enfoque reactivo basado en signals. Esta comparación examina ambas soluciones para ayudar a los desarrolladores Angular a tomar decisiones arquitectónicas informadas en 2026.

Guía de Decisión Rápida

Usar Signal Store para estado a nivel de funcionalidad y aplicaciones de complejidad media donde la reducción de boilerplate importa. Usar NgRx Clásico para aplicaciones empresariales que requieren pistas de auditoría estrictas, efectos secundarios complejos y depuración extensiva con DevTools.

Entendiendo las Diferencias Arquitectónicas

NgRx clásico sigue el patrón Redux: las acciones describen eventos, los reducers producen nuevo estado inmutable, y los selectors extraen datos. Cada cambio de estado fluye a través de un pipeline predecible y rastreable.

Signal Store adopta un enfoque diferente. En lugar de despachar acciones a través de reducers, el estado vive en signals reactivos. Las actualizaciones ocurren mediante métodos que llaman a patchState(), y los valores derivados provienen de signals computados en lugar de selectors memorizados.

| Aspecto | NgRx Clásico | Signal Store | |---------|--------------|---------------| | Modelo Mental | Redux/Flux | Servicios Reactivos | | Boilerplate | Alto (actions, reducers, selectors, effects) | Bajo (state, methods, computed) | | Reactividad | Observables RxJS | Signals Angular | | DevTools | Redux DevTools completo | Signal Store DevTools (vía plugin) | | Curva de aprendizaje | Pronunciada | Moderada | | Tamaño del Bundle | Mayor | Menor |

Para desarrolladores ya familiarizados con los Signals de Angular, Signal Store se siente como una extensión natural de las primitivas reactivas del framework.

NgRx Clásico: El Patrón Redux en Angular

NgRx clásico organiza la gestión de estado alrededor de actions, reducers, selectors y effects. Esta separación impone predictibilidad a costa de verbosidad.

books.actions.tstypescript
import { createActionGroup, emptyProps, props } from '@ngrx/store';
import { Book } from './book.model';

export const BooksActions = createActionGroup({
  source: 'Books',
  events: {
    'Load Books': emptyProps(),
    'Load Books Success': props<{ books: Book[] }>(),
    'Load Books Failure': props<{ error: string }>(),
    'Add Book': props<{ book: Book }>(),
    'Remove Book': props<{ id: string }>(),
  },
});

La API createActionGroup agrupa acciones relacionadas bajo una sola fuente, mejorando la legibilidad en DevTools y reduciendo el boilerplate comparado con llamadas individuales a createAction.

books.reducer.tstypescript
import { createReducer, on } from '@ngrx/store';
import { BooksActions } from './books.actions';
import { Book } from './book.model';

export interface BooksState {
  books: Book[];
  loading: boolean;
  error: string | null;
}

const initialState: BooksState = {
  books: [],
  loading: false,
  error: null,
};

export const booksReducer = createReducer(
  initialState,
  on(BooksActions.loadBooks, (state) => ({
    ...state,
    loading: true,
    error: null,
  })),
  on(BooksActions.loadBooksSuccess, (state, { books }) => ({
    ...state,
    books,
    loading: false,
  })),
  on(BooksActions.loadBooksFailure, (state, { error }) => ({
    ...state,
    loading: false,
    error,
  }))
);

Los reducers siguen siendo funciones puras que retornan nuevos objetos de estado. La función on() maneja la coincidencia de acciones y las actualizaciones de estado de forma concisa.

books.selectors.tstypescript
import { createFeatureSelector, createSelector } from '@ngrx/store';
import { BooksState } from './books.reducer';

export const selectBooksState = createFeatureSelector<BooksState>('books');

export const selectAllBooks = createSelector(
  selectBooksState,
  (state) => state.books
);

export const selectBooksLoading = createSelector(
  selectBooksState,
  (state) => state.loading
);

export const selectPublishedBooks = createSelector(
  selectAllBooks,
  (books) => books.filter((book) => book.published)
);

Los selectors proporcionan memorización automáticamente. El selector selectPublishedBooks solo recalcula cuando el array de libros cambia, haciendo las lecturas eficientes incluso en stores actualizados frecuentemente.

NgRx Signal Store: Gestión de Estado Signals-First

Signal Store consolida estado, valores computados y métodos en un único servicio inyectable. La documentación oficial de NgRx lo describe como una "solución de gestión de estado completamente equipada con soporte nativo para Signals de Angular."

books.store.tstypescript
import { computed, inject } from '@angular/core';
import { patchState, signalStore, withComputed, withMethods, withState } from '@ngrx/signals';
import { BooksService } from './books.service';
import { Book } from './book.model';

interface BooksState {
  books: Book[];
  loading: boolean;
  error: string | null;
}

const initialState: BooksState = {
  books: [],
  loading: false,
  error: null,
};

export const BooksStore = signalStore(
  { providedIn: 'root' },
  withState(initialState),
  withComputed(({ books }) => ({
    publishedBooks: computed(() => books().filter((book) => book.published)),
    totalBooks: computed(() => books().length),
  })),
  withMethods((store, booksService = inject(BooksService)) => ({
    async loadBooks() {
      patchState(store, { loading: true, error: null });
      try {
        const books = await booksService.getAll();
        patchState(store, { books, loading: false });
      } catch (error) {
        patchState(store, { loading: false, error: 'Failed to load books' });
      }
    },
    addBook(book: Book) {
      patchState(store, ({ books }) => ({ books: [...books, book] }));
    },
    removeBook(id: string) {
      patchState(store, ({ books }) => ({
        books: books.filter((book) => book.id !== id),
      }));
    },
  }))
);

Toda la gestión de estado de una funcionalidad cabe en un solo archivo. Las propiedades de estado se convierten en signals automáticamente, los signals computados reemplazan a los selectors, y los métodos reemplazan los despachos de acciones.

Gestión de Entidades con withEntities

Ambos enfoques soportan colecciones de entidades, pero la funcionalidad withEntities de Signal Store reduce drásticamente el código.

books-entity.store.tstypescript
import { computed, inject } from '@angular/core';
import { patchState, signalStore, withComputed, withMethods } from '@ngrx/signals';
import {
  addEntity,
  removeEntity,
  setAllEntities,
  updateEntity,
  withEntities,
} from '@ngrx/signals/entities';
import { BooksService } from './books.service';
import { Book } from './book.model';

export const BooksEntityStore = signalStore(
  { providedIn: 'root' },
  withEntities<Book>(),
  withComputed(({ entities }) => ({
    publishedBooks: computed(() => entities().filter((book) => book.published)),
  })),
  withMethods((store, booksService = inject(BooksService)) => ({
    async loadBooks() {
      const books = await booksService.getAll();
      patchState(store, setAllEntities(books));
    },
    addBook(book: Book) {
      patchState(store, addEntity(book));
    },
    updateBook(id: string, changes: Partial<Book>) {
      patchState(store, updateEntity({ id, changes }));
    },
    removeBook(id: string) {
      patchState(store, removeEntity(id));
    },
  }))
);

La funcionalidad withEntities proporciona automáticamente los signals ids, entityMap y entities. Los actualizadores de entidades como addEntity, removeEntity y updateEntity manejan las mutaciones de colecciones.

¿Listo para aprobar tus entrevistas de Angular?

Practica con nuestros simuladores interactivos, flashcards y tests técnicos.

Features de Store Personalizados para Reusabilidad

La función signalStoreFeature de Signal Store permite extraer y reutilizar preocupaciones transversales a través de los stores.

with-request-status.tstypescript
import { computed } from '@angular/core';
import { signalStoreFeature, withComputed, withState } from '@ngrx/signals';

type RequestStatus = 'idle' | 'pending' | 'fulfilled' | 'error';

export function withRequestStatus() {
  return signalStoreFeature(
    withState<{ requestStatus: RequestStatus }>({ requestStatus: 'idle' }),
    withComputed(({ requestStatus }) => ({
      isPending: computed(() => requestStatus() === 'pending'),
      isFulfilled: computed(() => requestStatus() === 'fulfilled'),
      isError: computed(() => requestStatus() === 'error'),
    }))
  );
}

export function setPending(): { requestStatus: RequestStatus } {
  return { requestStatus: 'pending' };
}

export function setFulfilled(): { requestStatus: RequestStatus } {
  return { requestStatus: 'fulfilled' };
}

export function setError(): { requestStatus: RequestStatus } {
  return { requestStatus: 'error' };
}

Este feature personalizado ahora puede componerse en cualquier store que necesite seguimiento del estado de carga.

books-with-status.store.tstypescript
import { inject } from '@angular/core';
import { patchState, signalStore, withMethods } from '@ngrx/signals';
import { setAllEntities, withEntities } from '@ngrx/signals/entities';
import { withRequestStatus, setFulfilled, setPending, setError } from './with-request-status';
import { BooksService } from './books.service';
import { Book } from './book.model';

export const BooksStore = signalStore(
  { providedIn: 'root' },
  withEntities<Book>(),
  withRequestStatus(),
  withMethods((store, booksService = inject(BooksService)) => ({
    async loadBooks() {
      patchState(store, setPending());
      try {
        const books = await booksService.getAll();
        patchState(store, setAllEntities(books), setFulfilled());
      } catch {
        patchState(store, setError());
      }
    },
  }))
);

El store compuesto ahora expone los signals computados isPending(), isFulfilled() e isError() sin duplicar lógica.

Patrones de Integración en Componentes

Signal Store se integra naturalmente con la sintaxis de templates de Angular mediante lecturas de signals.

books-list.component.tstypescript
import { Component, inject } from '@angular/core';
import { BooksStore } from './books.store';

@Component({
  selector: 'app-books-list',
  standalone: true,
  template: `
    @if (store.isPending()) {
      <div class="loading-spinner">Cargando libros...</div>
    }

    @if (store.isError()) {
      <div class="error-message">Error al cargar los libros</div>
    }

    @for (book of store.entities(); track book.id) {
      <div class="book-card">
        <h3>{{ book.title }}</h3>
        <p>{{ book.author }}</p>
        <button (click)="store.removeBook(book.id)">Eliminar</button>
      </div>
    }

    <p>Total: {{ store.totalBooks() }} libros</p>
  `,
})
export class BooksListComponent {
  readonly store = inject(BooksStore);

  constructor() {
    this.store.loadBooks();
  }
}

Sin gestión de suscripciones. Sin pipes async. Los signals se integran con la detección de cambios zoneless de Angular para un rendimiento óptimo.

Consideraciones de Rendimiento y Tamaño de Bundle

La arquitectura más ligera de Signal Store se traduce en bundles más pequeños. El paquete core @ngrx/signals añade aproximadamente 4KB comprimido con gzip, comparado con 15-20KB para el stack completo de NgRx clásico (@ngrx/store, @ngrx/effects, @ngrx/entity).

Signal Store también se beneficia de la detección de cambios basada en signals de Angular. Los signals computados solo recalculan cuando las dependencias cambian, y los componentes solo se re-renderizan cuando los signals consumidos se actualizan. Esta reactividad granular elimina ciclos innecesarios de detección de cambios comunes en patrones basados en Observables.

Para aplicaciones que ya usan RxJS extensivamente, los selectors basados en Observables de NgRx clásico se integran perfectamente con pipelines existentes.

Estrategia de Migración de Clásico a Signal Store

Migrar una aplicación NgRx clásica existente no requiere una reescritura completa. Ambas soluciones pueden coexistir, permitiendo adopción incremental.

  • Comenzar con nuevas funcionalidades usando Signal Store
  • Migrar módulos de funcionalidades aislados uno a la vez
  • Mantener funcionalidades complejas con muchos effects en NgRx clásico si el costo de migración excede el beneficio
  • Usar las utilidades de interoperabilidad de NgRx Signals para conectar estado basado en Observables y basado en signals
La Coexistencia Funciona

NgRx Store clásico y Signal Store pueden ejecutarse lado a lado en la misma aplicación. El equipo de NgRx soporta oficialmente ambos enfoques y proporciona utilidades de interoperabilidad para conectarlos.

Cuándo Elegir Cada Enfoque

Elegir Signal Store cuando:

  • Se construyen nuevas aplicaciones Angular 20+
  • Se gestiona estado a nivel de funcionalidad o componente
  • La experiencia del desarrollador y reducción de boilerplate son prioridad
  • El equipo tiene experiencia limitada con NgRx
  • El tamaño del bundle importa

Elegir NgRx Clásico cuando:

  • Se mantienen aplicaciones NgRx existentes
  • Se requieren pistas de auditoría estrictas con logging de acciones
  • Se construyen flujos de trabajo complejos con efectos secundarios extensivos
  • Se necesita depuración time-travel completa de Redux DevTools
  • El equipo tiene experiencia profunda en NgRx

Para los fundamentos de gestión de estado, entender ambos enfoques prepara a los desarrolladores para diversos codebases de Angular.

¡Empieza a practicar!

Pon a prueba tu conocimiento con nuestros simuladores de entrevista y tests técnicos.

Conclusión

  • Signal Store reduce el boilerplate de gestión de estado entre 60-70% comparado con NgRx clásico
  • NgRx clásico sigue siendo la mejor opción para aplicaciones empresariales que requieren pistas de auditoría estrictas y efectos secundarios complejos
  • Ambos enfoques pueden coexistir en la misma aplicación, permitiendo migración incremental
  • Los signals computados de Signal Store proporcionan memorización automática sin definiciones explícitas de selectors
  • Los features de store personalizados (signalStoreFeature) permiten preocupaciones transversales reutilizables
  • Para nuevos proyectos Angular 20+ en 2026, Signal Store debería ser la elección por defecto a menos que requisitos específicos demanden NgRx clásico

Compartir

Artículos relacionados