tutoriales.com

Gestión de Estado Reactiva con NgRx en Angular: Una Guía Completa

Este tutorial te guiará a través de los conceptos fundamentales de NgRx, la librería líder para la gestión de estado reactiva en aplicaciones Angular. Aprenderás a configurar NgRx, definir acciones, reductores y selectores, y manejar efectos secundarios con ejemplos prácticos para construir aplicaciones escalables y mantenibles.

Intermedio15 min de lectura7 views16 de marzo de 2026Reportar error

La gestión del estado en aplicaciones Angular puede volverse compleja a medida que crecen. NgRx, una implementación de la arquitectura Redux para Angular, proporciona un patrón de arquitectura predecible y robusto para manejar el estado, haciendo que tus aplicaciones sean más fáciles de depurar, probar y mantener. En este tutorial, exploraremos los componentes clave de NgRx y cómo integrarlos en tus proyectos.

🎯 ¿Qué es NgRx y por qué usarlo?

NgRx es un conjunto de librerías que implementa el patrón Redux para aplicaciones Angular. Su propósito principal es centralizar y gestionar el estado de tu aplicación de manera reactiva, ofreciendo un flujo de datos unidireccional. Pero, ¿por qué es tan importante?

  • Estado Centralizado: Todas las partes de tu aplicación acceden al mismo "Store" para obtener y actualizar el estado. Esto elimina la necesidad de pasar datos a través de múltiples componentes.
  • Predecibilidad: Las mutaciones del estado se realizan a través de funciones puras (reducers), lo que garantiza que el mismo estado inicial y la misma acción siempre producirán el mismo estado resultante.
  • Depuración Potente: Con herramientas como Redux DevTools, puedes ver cada acción que se despacha y cómo cambia el estado de tu aplicación con el tiempo, facilitando la depuración.
  • Facilidad de Prueba: Dado que los reducers son funciones puras, son increíblemente fáciles de probar de forma aislada.
  • Escalabilidad: A medida que tu aplicación crece, NgRx te ayuda a mantener la cordura organizando la lógica de estado de una manera modular y estructurada.
💡 Consejo: Piensa en NgRx como una única fuente de verdad para todos los datos de tu aplicación que necesitan ser compartidos o persistidos.

🛠️ Componentes Clave de NgRx

NgRx se compone de varios módulos principales que trabajan juntos para gestionar el estado. Vamos a desglosar cada uno:

Store (@ngrx/store) 📦

El Store es el corazón de NgRx. Es un servicio observable que contiene el estado de tu aplicación. Es inmutable, lo que significa que cada vez que el estado cambia, se crea un nuevo objeto de estado.

  • Store: El servicio que contiene el estado. Puedes dispatch acciones para cambiar el estado y select partes del estado para leerlas.

Actions (Acciones) 🚀

Las Actions son eventos únicos y discretos que describen lo que ha sucedido en tu aplicación. Son la única forma de iniciar un cambio de estado en el Store. Cada acción tiene un type único (string) y puede llevar un payload (carga útil) con datos relevantes.

// app.actions.ts
import { createAction, props } from '@ngrx/store';

export const loadItems = createAction('[Items Page] Load Items');

export const loadItemsSuccess = createAction(
  '[Items API] Load Items Success',
  props<{ items: any[] }>()
);

export const loadItemsFailure = createAction(
  '[Items API] Load Items Failure',
  props<{ error: any }>()
);

export const addItem = createAction(
  '[Items Page] Add Item',
  props<{ item: any }>()
);
📌 Nota: Es una buena práctica definir acciones para cada etapa de una operación asíncrona (iniciar, éxito, fallo).

Reducers (Reductores) 🔄

Los Reducers son funciones puras que toman el estado actual de la aplicación y una acción, y devuelven un nuevo estado. Son puros porque no tienen efectos secundarios: dada la misma entrada, siempre producirán la misma salida.

// app.reducer.ts
import { createReducer, on } from '@ngrx/store';
import * as AppActions from './app.actions';

export interface AppState {
  items: any[];
  loading: boolean;
  error: any | null;
}

export const initialAppState: AppState = {
  items: [],
  loading: false,
  error: null,
};

export const appReducer = createReducer(
  initialAppState,
  on(AppActions.loadItems, (state) => ({
    ...state,
    loading: true,
    error: null,
  })),
  on(AppActions.loadItemsSuccess, (state, { items }) => ({
    ...state,
    items: items,
    loading: false,
    error: null,
  })),
  on(AppActions.loadItemsFailure, (state, { error }) => ({
    ...state,
    loading: false,
    error: error,
  })),
  on(AppActions.addItem, (state, { item }) => ({
    ...state,
    items: [...state.items, item],
  }))
);
🔥 Importante: Los reductores NUNCA deben mutar el estado directamente. Siempre deben devolver un nuevo objeto de estado. Usa el operador spread (`...`) para esto.

Selectors (Selectores) 🔍

Los Selectors son funciones puras que se utilizan para seleccionar, derivar y componer fragmentos del estado del Store. Son eficientes porque memorizan los resultados (memoización), lo que significa que no recalcularán un valor si sus entradas no han cambiado.

// app.selectors.ts
import { createFeatureSelector, createSelector } from '@ngrx/store';
import { AppState } from './app.reducer';

export const selectAppState = createFeatureSelector<AppState>('app');

export const selectAllItems = createSelector(
  selectAppState,
  (state: AppState) => state.items
);

export const selectLoading = createSelector(
  selectAppState,
  (state: AppState) => state.loading
);

export const selectError = createSelector(
  selectAppState,
  (state: AppState) => state.error
);

export const selectItemsCount = createSelector(
  selectAllItems,
  (items) => items.length
);

Effects (Efectos) ✨

Los Effects son la forma en que NgRx maneja los efectos secundarios, como las llamadas HTTP a una API, el acceso al almacenamiento local o la navegación del router. Son clases observables que escuchan las acciones despachadas y reaccionan a ellas, a menudo despachando nuevas acciones una vez que se completa el efecto secundario.

// app.effects.ts
import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { of } from 'rxjs';
import { catchError, map, mergeMap } from 'rxjs/operators';
import * as AppActions from './app.actions';
import { ItemsService } from './items.service'; // Servicio de ejemplo para API

@Injectable()
export class AppEffects {
  loadItems$ = createEffect(() =>
    this.actions$.pipe(
      ofType(AppActions.loadItems),
      mergeMap(() =>
        this.itemsService.getAllItems().pipe(
          map((items) => AppActions.loadItemsSuccess({ items })),
          catchError((error) => of(AppActions.loadItemsFailure({ error })))
        )
      )
    )
  );

  constructor(private actions$: Actions, private itemsService: ItemsService) {}
}
⚠️ Advertencia: Los efectos deben ser transparentes. No deben tener estado propio ni mutar el estado directamente. Siempre deben despachar nuevas acciones.

🚀 Configurando NgRx en tu Proyecto Angular

Ahora que conocemos los componentes, veamos cómo configurar NgRx en una aplicación Angular.

1. Instalar las Librerías 💾

Primero, necesitas instalar los paquetes @ngrx/store y @ngrx/effects (y opcionalmente @ngrx/store-devtools para la depuración).

npm install @ngrx/store @ngrx/effects @ngrx/store-devtools --save

2. Definir el Estado y Reductores 📝

Crea tus interfaces de estado, estado inicial y reductores como se mostró en la sección de Reducers.

src/app/store/app.reducer.ts src/app/store/app.actions.ts src/app/store/app.selectors.ts

3. Configurar el Store en AppModule ⚙️

Importa StoreModule y EffectsModule en tu AppModule y registra tus reductores y efectos.

// src/app/app.module.ts
import { NgModule, isDevMode } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { appReducer } from './store/app.reducer';
import { AppEffects } from './store/app.effects';
import { AppComponent } from './app.component';
import { HttpClientModule } from '@angular/common/http'; // Para los efectos HTTP

@NgModule({
  declarations: [AppComponent],
  imports: [
    BrowserModule,
    HttpClientModule,
    StoreModule.forRoot({ app: appReducer }), // Registra tu reducer principal
    EffectsModule.forRoot([AppEffects]), // Registra tus efectos
    StoreDevtoolsModule.instrument({ maxAge: 25, logOnly: !isDevMode() }), // Herramientas de depuración
  ],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

Aquí, StoreModule.forRoot({ app: appReducer }) indica que tenemos un único slice de estado llamado app gestionado por appReducer.

¿Qué es un Feature Store? Un Feature Store (`StoreModule.forFeature()`) se usa cuando quieres dividir tu estado en módulos más pequeños que pertenecen a *características* específicas de tu aplicación. Esto ayuda a la modularidad y a la carga perezosa (lazy loading).

👩‍💻 Interactuando con el Store desde Componentes

Una vez que NgRx está configurado, puedes interactuar con el Store desde cualquier componente o servicio.

Despachar Acciones (dispatch)

Para cambiar el estado, debes despachar una acción. Inyecta el Store y usa el método dispatch.

// src/app/app.component.ts
import { Component, OnInit } from '@angular/core';
import { Store } from '@ngrx/store';
import * as AppActions from './store/app.actions';
import { AppState } from './store/app.reducer';
import { Observable } from 'rxjs';
import { selectAllItems, selectLoading, selectError } from './store/app.selectors';

@Component({
  selector: 'app-root',
  template: `
    <h1>Lista de Elementos</h1>
    <button (click)="loadItems()">Cargar Elementos</button>
    <button (click)="addNewItem()">Agregar Elemento</button>

    <div *ngIf="(loading$ | async)">Cargando elementos...</div>
    <div *ngIf="(error$ | async) as error">Error: {{ error.message }}</div>

    <ul>
      <li *ngFor="let item of (items$ | async)">{{ item.name }}</li>
    </ul>
  `,
})
export class AppComponent implements OnInit {
  items$: Observable<any[]>;
  loading$: Observable<boolean>;
  error$: Observable<any | null>;

  constructor(private store: Store<AppState>) {
    this.items$ = this.store.select(selectAllItems);
    this.loading$ = this.store.select(selectLoading);
    this.error$ = this.store.select(selectError);
  }

  ngOnInit(): void {
    // Opcional: cargar al inicio
    // this.store.dispatch(AppActions.loadItems());
  }

  loadItems(): void {
    this.store.dispatch(AppActions.loadItems());
  }

  addNewItem(): void {
    const newItem = { id: Date.now(), name: 'Nuevo Elemento ' + Date.now() };
    this.store.dispatch(AppActions.addItem({ item: newItem }));
  }
}

Seleccionar el Estado (select)

Para leer el estado, usa el método select del Store con tus selectores definidos. Esto devuelve un Observable que emite un nuevo valor cada vez que la parte del estado seleccionada cambia.

// Dentro del constructor del componente:
this.items$ = this.store.select(selectAllItems);
this.loading$ = this.store.select(selectLoading);
this.error$ = this.store.select(selectError);

Usa el pipe async en tu plantilla para suscribirte automáticamente a estos observables y desuscribirte cuando el componente se destruye, previniendo fugas de memoria.

<div *ngIf="(loading$ | async)">Cargando elementos...</div>
<ul>
  <li *ngFor="let item of (items$ | async)">{{ item.name }}</li>
</ul>

📈 Arquitectura y Flujo de Datos con NgRx

Comprender el flujo de datos es fundamental para trabajar con NgRx. Se adhiere estrictamente a un flujo de datos unidireccional.

Actions Effects Reducer Store Selector Component Despacha Inicia Actualiza Notifica Lee estado API Nuevo Action

El flujo es el siguiente:

  1. Componente/Servicio Despacha una Acción: Algo ocurre en la interfaz de usuario (un clic de botón, por ejemplo) o en un servicio que requiere un cambio de estado. Se despacha una Action que describe este evento.
  2. La Acción llega a los Reducers y Effects:
    • Reducers: El reducer relevante toma la acción y el estado actual, y devuelve un nuevo objeto de estado inmutable.
    • Effects: Si la acción requiere una operación asíncrona (como una llamada HTTP), un Effect la interceptará. El Effect realiza la operación y, una vez completada, despacha una nueva acción (por ejemplo, loadItemsSuccess o loadItemsFailure).
  3. El Store se Actualiza: El Store reemplaza su estado anterior con el nuevo estado generado por el reducer.
  4. Los Selectors Emiten Nuevos Valores: Cualquier selector que esté observando la parte del estado que cambió emitirá un nuevo valor.
  5. Los Componentes se Actualizan: Los componentes suscritos a esos selectores (típicamente con el pipe async) se renderizan de nuevo con los datos actualizados.
💡 Consejo: Visualizar este ciclo te ayudará a depurar y entender por qué tu aplicación no se comporta como esperas.

✅ Buenas Prácticas y Consejos Avanzados

Para maximizar los beneficios de NgRx y mantener tu aplicación limpia:

  • Modularización con Feature Stores: Para aplicaciones grandes, divide tu estado en módulos de características usando StoreModule.forFeature() y EffectsModule.forFeature(). Esto ayuda a la carga perezosa y a mantener el código organizado.

  • Inmutabilidad Rigurosa: Asegúrate de que tus reductores siempre devuelvan nuevos objetos de estado. Herramientas como immer pueden ayudarte, aunque NgRx por defecto lo gestiona si usas createReducer correctamente.

  • Selectores Composables: Construye selectores pequeños y composables. Por ejemplo, selectUserById puede usar selectAllUsers.

  • Unit Testing: Prueba tus reductores y selectores exhaustivamente, ya que son funciones puras. Los efectos requieren un poco más de configuración para probar observables.

  • Evitar el Estado Local Excesivo: Siempre que un estado deba ser compartido entre componentes hermanos, o persista a través de navegaciones, considera llevarlo al Store de NgRx.

  • NGRX Schematics: Usa @ngrx/schematics para generar automáticamente archivos boilerplate (acciones, reductores, efectos, selectores) y acelerar el desarrollo.

    ng generate feature Auth --module app.module --flat false

    Esto generará una carpeta +auth con las acciones, reductores y efectos para una característica Auth.

  • Monitoreo con Redux DevTools: Instala la extensión de navegador Redux DevTools. Es indispensable para depurar y comprender el flujo de tu aplicación NgRx.

    90% Rendimiento de Depuración

Tabla Comparativa: Estado Local vs. NgRx

CaracterísticaEstado Local (Input/Output, Servicios)NgRx (Store)
ComplejidadFácil para apps pequeñasMás boilerplate para apps pequeñas, ideal para grandes
DepuraciónDifícil de rastrear cambiosPotente con DevTools, historial de acciones
EscalabilidadDisminuye con el crecimiento de la appAlta, estructura organizada
Flujo de DatosBidireccional, spaghetti-code posibleUnidireccional y predecible
PruebasRequiere mocking de dependenciasReducers y Selectors fáciles de probar
RendimientoPuede causar re-renderizados innecesariosOptimizado con selectores memorizados
⚠️ Advertencia: No uses NgRx para *todo* el estado de tu aplicación. El estado local de componentes (por ejemplo, el valor de un campo de entrada temporal) es perfectamente válido. NgRx es para el estado *global* o *compartido*.

🔚 Conclusión

NgRx es una herramienta poderosa para la gestión de estado en aplicaciones Angular, proporcionando una arquitectura robusta, predecible y escalable. Aunque puede tener una curva de aprendizaje inicial debido a su naturaleza un tanto opinada y el boilerplate que introduce, los beneficios a largo plazo en términos de mantenibilidad, depuración y pruebas superan con creces el esfuerzo inicial. Al dominar sus conceptos fundamentales (acciones, reductores, selectores y efectos), estarás bien equipado para construir aplicaciones Angular complejas y de alto rendimiento.

¡Anímate a integrarlo en tu próximo proyecto y experimenta la potencia de la gestión de estado reactiva!

Tutoriales relacionados

Comentarios (0)

Aún no hay comentarios. ¡Sé el primero!