Redux con Angular: NgRx

Redux es una metodología para administrar el estado de la aplicación, inicialmente popularizada para React, pero que gracias a NgRx se puede aprovechar al máximo también en Angular.
NgRx en una implementación de Redux que se define como un framework para crear aplicaciones reactivas en Angular, y para ello hace uso de RxJS, que le proporciona la posibilidad de utilizar observables, mediante los que gestionar el flujo de información.
Fundamentos de Redux
Redux se basa en tres principios básicos:
- Única fuente de verdad: el estado de la aplicación se almacena en un único lugar, llamado store.
- El estado es de solo lectura: desde la store donde se almacena la información se pueden crear suscripciones a los datos, pero no se pueden modificar directamente.
- Los cambios se realizan median funciones puras: para modificar los datos se utilizan las acciones, que contienen el identificador de la propia acción y en opcionalmente un payload (la información asociada a la acción). Los cambios aplicados solo pueden hacer uso de estos datos, sin recurrir a ningún tipo de información externa, para generar un nuevo estado de la aplicación, de forma que siempre que se ejecute esa acción con esos datos el resultado sea siempre el mismo.
El funcionamiento de Redux se puede explicar mediante estos conceptos:
- Estado: es el objeto o conjunto de objetos que contienen la información de la aplicación.
- Store: es el elemento que almacena el estado. Desde aquí se distribuye a los distintos puntos de la aplicación como copias de solo lectura.
- Acciones: representan el tipo de cambio que hay que realizar en el estado de la aplicación, y en caso de ser necesario, los datos que haya que introducir o modificar.
- Reducers: se encargan de efectuar los cambios en el estado de la aplicación. Son funciones puras que reciben como parámetros el estado actual y la acción que se va a llevar a cabo y devuelven una nueva copia del estado una vez modificado.
NgRx en acción
NgRx proporciona control de estado reactivo de la aplicación, aislamiento de efectos colaterales (como peticiones ajax), gestión de colecciones de entidades (los datos de la aplicación), gestión de rutas, generación de código y herramientas de desarrollo para depurar el código por medio de estos paquetes:
- Store: gestión del estado reactivo de la aplicación basado en RxJS.
- Store Devtools: herramientas para depurar desde el navegador.
- Effects: aisla las funciones asíncronas para mantener el resto de componentes mediante funciones puras.
- Router Store: conecta el router the Angular con el store.
- Entity: se encarga de gestionar los datos almanenados en el estado de la aplicación. En lugar de almacenarse los datos de la aplicación directamente como arrays se utilizan mapas con un id único que identifica al objeto que almacena la información.
- Schematics: integra en Angular CLI los comandos para generar la estructura de código sobre el que escribir componentes NgRx.
Para instalar el paquete completo de utilidades de NgRx con npm se utilizan los siguientes comandos:
npm install @ngrx/schematics --save-dev
npm install @ngrx/{store,router-store,effects,entity,store-devtools} --save
Con Schematics se puede generar la estructura de los distintos paquetes de NgRx siguiendo el siguiente patrón: ng generate <tipo> <nombre>
Las acciones disponibles son las siguientes:
- Store: genera un estado para la aplicación y lo registra en el módulo indicado.
- Action: genera un conjunto de acciones genéricas para añadir, actualizar y eliminar datos en el store.
- Reducer: genera un reductor, el estado, el estado inicial de la aplicación y un EntityAdapter.
- Container: genera un componente de Angular con un store inyectado en el constructor.
- Effect: genera un efecto y opcionalmente lo registra en el módulo indicado.
- Entity: genera el conjunto de archivos relacionados con las entidades. Acción, modelo y reductor. En el archivo del reductor también se inicializa el EntityAdapter.
- Feature: genera los archivos que contienen: acciones, reductor y efectos.
Se puede generar una entidad compuesta por Action, Reducer y modelo y añadir a app.module.ts la inicialización de reductor mediante el siguiente comando:
ng generate @ngrx/schematics:entity Nombre --module app.module.ts --spec --flat false
Una vez generada la estructura, en el modelo habría que espedificar los datos que debe contener. Inicialmente tan solo se incluye el Id:
export interface Nombre { id: string}
Tras esto, en el componente donde se vaya a hacer uso del store, además de importar las dependencias necesarias, habría que inyectar el store en el constructor del componente y seleccionar los datos que se van a utilizar:
constructor(private store: Store
this.nombres = store.select('nombre').pipe(
map(state => Object.values(state.entities))
)
}
Inicialmente store.select devuelve el estado de la aplicación, por lo que hay que extraer posteriormente los datos que almacena como entidades mediante map(state => Object.values(state.entities)).
Para realizar cambios en los datos hay que importar previamente las acciones existentes en el archivo de acciones generado por schematics, mediante import * as fromLink from ‘../nombre/nombre.actions’;
Teniendo la instancia del store y las acciones disponibles ya se pueden realizar cambios en los datos de la aplicación. Para ello se utiliza el método dispatch del store, pasándole de parámetro una acción, que se crea directamente de las acciones prefedinidas creadas por schematics, pasándole lo datos correspondientes (payload) para crear, actualizar o eliminar valores.
// Añadir valores const payload: Nombre = {id: this.nextId, name: this.name}; this.store.dispatch(new fromLink.AddNombre({ nombre: payload}));
// Eliminar valores const payload = { id: id }; this.store.dispatch(new fromLink.DeleteNombre(payload));
Al generar desde schematics una entidad el fichero de acciones incluye todas las acciones referentes a añadir, actualizar y eliminar elementos, de forma que se pueden llamar a estas acciones sin necesidad de escribir código alguno.
En este repositorio se puede examinar un ejemplo completo con una aplicación generada con schematics haciendo uso de entidades para gestionar la información de la aplicación.