🇺🇸 EN
Frontend
·8 min read·↗·🇺🇸 EN

Cuándo y por qué usar REDUX NgRx en Angular

Portada principal del artículo: Cuándo y por qué usar REDUX NgRx en Angular

En un artículo anterior escribí sobre cómo manejar el estado en Angular usando servicios, pero cuando la aplicación crece y necesita rastrear y gestionar un estado grande, la situación se vuelve compleja y difícil de mantener. Por ejemplo, cuando tenemos muchos componentes que necesitan reaccionar a esos cambios de estado.

Al principio, usar un servicio con un BehaviorSubject es sencillo, pero a veces empezamos a encontrar problemas o efectos secundarios que hacen difícil identificar exactamente dónde ocurre el error.

En nuestras aplicaciones solemos manejar varios tipos de estado: a veces persistente en el backend y proveniente de una API, en la URL como http://myapp.com/users?id=2, o dentro de un solo componente. Entonces, ¿cuándo necesito realmente usar NgRx? 🤔

¿Realmente necesitas usar NgRx?

NgRx es una gran herramienta, pero no la uso en todas mis aplicaciones. ¿Por qué? Antes de empezar con NgRx, siempre me hago estas preguntas:

  • ¿Mi aplicación es pequeña o es solo un MVP?

  • ¿Necesito manejar estado, pero mis componentes no necesitan reaccionar a cambios en otros lados?

  • ¿Mis compañeros de equipo conocen RxJS y Redux?

Si respondiste que sí, entonces no necesitas NgRx. NgRx requiere un buen conocimiento de RxJS e implica escribir bastante código repetitivo (boilerplate). Por lo tanto, si estás trabajando en algo pequeño o entregando un MVP, te recomiendo construir la solución usando servicios de Angular con BehaviorSubject o considerar @ngrx/component-store como una buena alternativa.

Pero, si este no es tu escenario y quieres avanzar con NgRx, hazte las siguientes preguntas sobre tu estado:

  • ¿Lo consumen muchos componentes y servicios?

  • ¿Es persistente y cambia a través de distintas fuentes?

  • ¿Cambia por acciones provenientes de otros lugares?

  • ¿Necesita estar sincronizado o disponible para las rutas?

  • ¿Se obtiene a través de un efecto secundario (side effect)?

Si la mayoría de estas respuestas son afirmativas, entonces sí necesitas usar NgRx. La mejor manera de entender si realmente lo necesitas es con un ejemplo real: la web de NBA.com. Tiene muchísimas interacciones del usuario, necesita mantener el estado en el router, sincronizar componentes, reaccionar a cambios en el feed en tiempo real y manejar puntos como:

  • La URL está vinculada con el estado del calendario y los filtros.

  • Los calendarios filtran la información y el usuario puede cambiarlos, lo cual también actualiza la URL.

  • El feed de partidos debe reaccionar a cambios en tiempo real.

Cuando el usuario realiza muchas interacciones y los componentes deben reaccionar a múltiples fuentes de datos, NgRx ofrece un conjunto de librerías como @ngrx/store, @ngrx/effects, @ngrx/router-store, @ngrx/entity, @ngrx/component-store, @ngrx/signals y @ngrx/operators. Estas herramientas nos ayudan a construir un estado global y reactivo aislando los efectos secundarios, integrándose con el router de Angular, gestionando colecciones de entidades y ofreciendo una forma muy sencilla de depurar usando Redux DevTools.

NgRx facilita organizar y manejar el estado porque sigue el patrón REDUX, por lo que tenemos:

  • Una única fuente de la verdad: el store.

  • El estado es de solo lectura: la única forma de modificarlo es despachando acciones.

  • Los reducers son funciones puras responsables de cambiar el estado.

  • Ofrece Selectors para obtener partes específicas del estado.

Todas estas piezas siguen un flujo de trabajo claro, donde cada una cumple una responsabilidad específica en el proceso. Veamos cómo funciona este flujo en NgRx.

Flujo de trabajo en NgRx

Por ejemplo, en mi aplicación de Angular, el componente home.component requiere que el usuario acepte los términos y condiciones. Luego de aceptarlos, el usuario navega a la página About. Sin embargo, cuando regresa a Home, la casilla de verificación vuelve a aparecer desmarcada 🙃.

¿Por qué mi aplicación no mantiene el estado?

El motivo es que el componente home.component se destruyó al navegar a About y el estado desapareció con él, ya que no se estaba guardando en ningún lugar. Me parece un caso sencillo y perfecto para resolver con NgRx y mostrar cómo funciona su flujo de trabajo.

Sí, es un caso muy fácil de resolver con un BehaviorSubject, pero es la forma más clara de mostrar el flujo de trabajo de NgRx.

El flujo de NgRx se basa en Actions, Reducers, Store y Selectors (junto con Effects cuando son necesarios):

  • El componente dispara una acción: [TermsCondition Page] Accept Terms Conditions.

  • El reducer recibe la acción disparada, toma una copia del state actual, aplica los cambios y actualiza el estado.

  • El componente lee el cambio en el estado a través de los selectors o directamente desde el store global.

Configuración del proyecto

En lugar de construir una aplicación desde cero, preparé un repositorio como punto de partida. En este ejemplo uso Angular con componentes Standalone (sin módulos).

Primero, abre tu terminal y ejecuta los siguientes comandos para clonar el repositorio e instalar las dependencias:

git clone https://github.com/danywalls/start-with-ngrx.git
npm i

Luego, ejecuta ng serve para iniciar la aplicación de ejemplo y abre tu navegador en http://localhost:4200. En la página de inicio, marca la casilla de verificación. Después, haz clic en "About" y vuelve a "Home". Verás que la casilla vuelve a estar desmarcada.

Ya tenemos el escenario listo, ¡es momento de solucionarlo usando NgRx! ⚒️

Configurar NgRx

Ahora instalamos NgRx ejecutando el comando npm i @ngrx/store:

npm i @ngrx/store

Abre el archivo app.config.ts. En la sección de proveedores (providers), usa la función provideStore para configurar el estado global. Esta función espera el estado y el reducer, pero como todavía no tenemos el reducer listo, la inicializamos vacía por ahora:

import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import { provideStore } from '@ngrx/store';
 
export const appConfig = {
  providers: [provideRouter(routes), provideStore()],
};

Ahora necesitamos definir el estado para la página de inicio. Crea un nuevo archivo en pages/home/home.state.ts. Dentro, crea una interfaz para HomeState y define el initialState:

El estado es un objeto con las propiedades que queremos exponer.

export interface HomeState {
  acceptTerms: boolean
}
const initialState: HomeState = {
  acceptTerms: false,
}

Con el estado listo, creamos el reducer en pages/home/state/home.reducer.ts. Importa la función createReducer junto con el initialState e importa la función on.

También importa la función createAction para definir la acción. Pasa el nombre de la acción [Home Page] Accept Terms y la función que actualiza el estado.

Usa el operador spread (...) para mantener los valores actuales del state y actualizar la propiedad acceptTerms.

El código final queda así:

import { createAction, createReducer, on } from '@ngrx/store';
import { initialState } from './home.state';
 
export const homeReducer = createReducer(
  initialState,
  on(createAction('[Home Page] Accept Terms'), (state) => ({
    ...state,
    acceptTerms: !state.acceptTerms,
  })),
);

Ya podemos inicializar el store. Abre de nuevo el archivo app.config.ts y agrega el slice de home al store usando homeReducer:

Otra alternativa moderna es usar provideState.

import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import {provideStore, StoreModule} from "@ngrx/store";
import {homeReducer} from "./pages/home/state/home.reducer";
 
export const appConfig = {
  providers: [provideRouter(routes),
   provideStore(
     {
       home: homeReducer,
     }
   )
  ]
};

Usar NgRx en el componente

Estamos en los pasos finales. Primero, abre el archivo home.component.ts e inyecta el Store. Luego, obtenemos $acceptTerms desde el store. Para este artículo no voy a entrar en los selectors, así que me suscribo directamente al store usando select y transformo el observable en una señal mediante la función toSignal, evitando tener que suscribirme con el pipe async.

Hacemos un pequeño cambio en el método onChange. Con el store, despachamos la acción para disparar la actualización en el reducer.

El código final se ve así:

export class HomeComponent {
  private _store = inject(Store);
  public $acceptTerms = toSignal(this._store.select((state) => state.home.acceptTerms))
 
  onChange() {
    this._store.dispatch({
      type: '[Home Page] Accept Terms'
    })
  }
}

En la plantilla, actualizamos la lógica para usar la señal $acceptTerms leyendo su valor con ():

@if(!$acceptTerms()) {
    <h3>Do you want to use NgRx?</h3>
    <input (change)="onChange()" [value]="$acceptTerms()" type="checkbox"  >
} @else {
   <h2>Thanks for love NgRx🎉🥳</h2>
}

Guarda los cambios y ¡listo! 🎉🎉🎉 NgRx ya está configurado y el estado queda integrado en nuestra aplicación.

Conclusión

Vimos lo sencillo que es crear un estado, una acción y un reducer, además de leer el estado directamente con el store. Para una aplicación pequeña puede sentirse como demasiado código repetitivo, pero es un excelente punto de partida para entender NgRx.

También revisamos cuándo conviene usar NgRx en lugar de servicios de Angular. Puedes usar las preguntas de la lista para decidir si tu proyecto realmente lo necesita.

Con este ejemplo práctico vimos cómo configurar NgRx para manejar el estado: configurar el store, crear el estado inicial, definir reducers e integrarlo en un componente para mantener el estado entre navegaciones.

Parte de la serie Frontend

Modern frontend development — Angular, React, TypeScript, accessibility, and performance.

Ver serie completa

Preguntas Frecuentes

¿Cuándo debería usar NgRx en lugar de servicios de Angular con BehaviorSubject para manejar el estado?

Deberías considerar NgRx cuando tu aplicación tiene un estado compartido entre muchos componentes y servicios, se actualiza desde múltiples fuentes, necesita sincronizarse con la URL o involucra efectos secundarios complejos como llamadas a APIs. Para aplicaciones pequeñas o MVPs, los servicios de Angular con BehaviorSubject son más simples y requieren mucho menos código repetitivo.

¿Cuáles son los bloques principales de NgRx y cómo interactúan entre sí?

NgRx se basa en cuatro conceptos clave: las Actions describen eventos disparados desde componentes o effects, los Reducers son funciones puras que actualizan el estado según esas acciones, el Store es la única fuente de la verdad que contiene todo el estado de la aplicación, y los Selectors son funciones que obtienen partes específicas del estado. Los Effects manejan efectos secundarios asíncronos como llamadas HTTP entre las acciones y el store.

¿Es NgRx demasiado para una aplicación pequeña en Angular y cuáles son las alternativas?

Sí, NgRx puede ser excesivo para aplicaciones pequeñas o medianas porque requiere conocimientos de RxJS y patrones Redux, además de implicar bastante código repetitivo (boilerplate). Buenas alternativas incluyen servicios de Angular con BehaviorSubject para estados compartidos simples, o el NgRx Component Store oficial, que ofrece una solución más liviana y acotada al componente sin toda la sobrecarga de Redux.

Artículos Relacionados

Compartir este artículo

Si esta guía te resultó útil, compártela con tu equipo o con otros desarrolladores.


Software real. Lecciones reales.

Comparto las lecciones que aprendí por las malas, para que puedas evitarlas o estar preparado cuando ocurran.

User avatar
User avatar
User avatar
User avatar
+13K

Únete a 13,800+ desarrolladores y lectores.

Cero spam. Cancela tu suscripción en cualquier momento.