Fundamentos de los Reducers en NgRx

Reducers — Dominando los Conceptos de Inmutabilidad y Funciones puras

Rodrigo Bosarreyes
6 min readJan 23, 2024
Photo by Sigmund on Unsplash

¿Qué son los Reducers en NgRx?

Son funciones puras que transicionan un estado a otro en función de una acción. Cada reducer se asocia con una porción específica del estado y se encarga de actualizar ese estado.

¿Qué es una función pura?

Una función pura tiene dos características clave:

  • ✅ Determinismo: Siempre devuelve el mismo resultado si se le pasan los mismos argumentos. No depende de y no modifica el estado de variables externas a la función.
  • ✅ No tiene efectos secundarios: No altera ningún estado fuera de su alcance. Esto significa que no modifica variables globales, no escribe en una base de datos y no realiza acciones que afecten algo fuera de la función.

Vamos a verlo con un ejemplo:

let factor = 5;

function impureAdd(value: number): number {
return value + factor; // Resultado depende de una variable global
}

// Ejemplo de llamada:
// impureAdd(10) podría retornar diferentes resultados si 'factor' cambia.

En este caso, impureAdd es una función impura porque su resultado no solo depende de su entrada (value) sino también del valor de una variable global (factor). Si factor cambia, el resultado de impureAdd para el mismo valor de entrada cambiará, lo que la hace no determinista.

Ahora, la refactorizamos para que sea pura:

function pureAdd(value: number, addend: number): number {
return value + addend; // Resultado solo depende de los argumentos de la función
}

// Ejemplo de llamada:
// pureAdd(10, 5) siempre retornará 15, sin importar factores externos.

pureAdd es una función pura y determinista. Dados los mismos argumentos, siempre producirá el mismo resultado. No depende de ningún estado o variable externa. Esto la hace predecible y más fácil de testear.

Cuando se dispara una acción con NgRx, esta acción, junto con el estado actual, se envía a un reducer correspondiente. El reducer evalúa el tipo de acción y, basándose en esta, produce un nuevo estado. Importante destacar que los reducers no modifican el estado actual directamente; en su lugar, generan un nuevo estado con las modificaciones necesarios.

Recordemos que la inmutabilidad es un concepto clave en el patrón Redux y, por extensión, también en NgRx.

¿Qué es la inmutabilidad?

Inmutabilidad en programación significa que una vez que un objeto es creado, su estado no puede ser modificado, solo reemplazado.

Si esto lo extrapolamos al contexto de NgRx, podemos decir que los reducers son funciones que toman el estado actual y una acción como argumentos, y devuelven un nuevo estado. Aquí está la esencia de la inmutabilidad en este contexto:

  • ✅ No Modificar el Estado Actual: En lugar de cambiar el estado existente (por ejemplo, añadiendo un elemento a un array o actualizando un valor en un objeto), se crea una copia del estado y se efectúan las modificaciones en esta copia.
  • ✅ Retornar un Nuevo Estado: Después de aplicar los cambios necesarios, el reducer retorna esta nueva versión del estado, en lugar del original. Esto asegura que el estado anterior permanece intacto y no se ve afectado por las nuevas acciones.

¿Cómo se crean Reducers?

Como hemos visto, la responsabilidad de los reductores es transformar el estado de una manera pura e inmutable. Para crearlos podemos utilizar el método createReducer.
Este método recibe como primer argumento el estado inicial del estado, a partir del primer argumento vamos a definir la respuesta o la función que se ejecutará cuando se lance una acción, esto lo hacemos con el método on.

El método on recibe como primer argumento un Action y como segundo argumento un arrow function que se encargará de realizar esa modificación del estado, a su vez, esta función recibe dos parámetros. el estado y la acción con el payload.

Cuando una acción es lanzada, todos los reductores registrados recibirán la acción, pero solamente se ejecutará la función que tenga un on asociado a la acción.

Para registrar los reducers, debemos ir a archivo de configuración de módulo principal. En aplicaciones Standalone (Angular 17+) se encuentra en app.config.ts. En este archivo, en la sección de providers, podemos registrarlo de dos maneras:

  • 🌍 A nivel global: Para registrarlo a nivel global debemos pasarle un map donde la clave es el nombre del dominio y el valor es el reductor.
import { bootstrapApplication } from '@angular/platform-browser';
import { provideStore } from '@ngrx/store';

import { AppComponent } from './app.component';
import { cartReducer } from './reducers/cart.reducer';

bootstrapApplication(AppComponent, {
providers: [
provideStore({ cart: cartReducer })
],
});
  • A nivel “feature”: Todavía no hemos visto el concepto de feature, pero te lo adelanto, como resumen una feature es algo parecido a los antiguos módulos de Angular, simplemente son un contenedor virtual. Si queremos registrar un reductor a un “módulo”, debemos utilizar la función provideState y además, debemos cambiarlo en el archivo de rutas.
import { Route } from '@angular/router';
import { provideState } from '@ngrx/store';

import { cartFeatureKey, cartReducer } from './reducers/cart.reducer';

export const routes: Route[] = [
{
path: 'cart',
providers: [
provideState({ name: cartFeatureKey, reducer: cartReducer })
]
}
];
Alto allí

Este es artículo es parte de mi curso de Introducción a NgRx, donde abordamos temas como Store, Actions, Reducers, Selectors, Effects y Features. Si no te suena algún tema este artículo, ¡te recomiendo revisar mi curso!

Implementando reducers en nuestra aplicación

Vamos a recordar nuestra maravillosa aplicación 🎉Ng Party Planner🎉, en el artículo anterior implementamos todas las acciones que tendrá nuestra aplicación, pero estas acciones no son nada sin sus reductores, ¡así que vamos a implementarlos!

¡Recuerda que puedes acceder al repositorio para ver el código cambiado en este módulo!

guest.reducer.ts

Ahora, sin realizar ningún cambio nuevo (te recuerdo que en el artículo anterior implementamos sus acciones), deberíamos tener el siguiente resultado:

Et voilà. Esto sería todo, como en el artículo de las acciones implementamos todas estas invocaciones no nos tenemos que preocupar de nada más. Al tener un patrón que distribuye tan claramente cada una de las responsabilidades nos da la ventaja de podemos modificar algo de manera aislada, sin que afecte a las otras partes, eso sí, a cambio de una mayor complejidad en el código.

En este artículo hemos visto que un reducer se utiliza para actualizar el estado y que para ello debemos procurar crear funciones puras e inmutables, además, también hemos visto que para crear reducers debemos utilizar la función createReducer y hemos visto varios ejemplos prácticos, pero ¿cómo visualizamos el estado? ¿Cómo podemos reflejar todos esos cambios? ¡Acompáñame para presentarte a los Selectors!

--

--