Fundamentos de Stores en NgRx
Explorando Stores — Creación, Acceso y Actualizaciones
¿Qué es un Store en NgRx?
Un store, como su propio nombre indica, es un almacén que contiene el estado de la aplicación. Un Store permite gestionar el estado de manera centralizada. Tan simple como eso.
¿Cuándo utilizar Store?
Para saber cuándo deberíamos utilizar Store, desde el equipo de NgRx nos recomiendan utilizar el principio SHARI:
- (S)hared: El estado es compartido en varias partes de la aplicación. Un buen ejemplo sería la autenticación y configuración del usuario.
- (H)ydrated: El estado persiste y se “hidrata” de sistemas externos. Un buen ejemplo sería obtener datos del Local Storage.
- (A)vailable: El estado debe estar disponible al entrar en rutas. Un buen ejemplo podría ser los “Wizard” (un asistente que nos marca paso a paso la acción que queremos realizar)
- (R)etreived: El estado se recibe mediante un efecto secundario. El más claro ejemplo sería toda aquella información que necesitamos obtener mediante llamadas a una API.
- (I)mpacted: El estado es impactado por acciones de otras fuentes. Por ejemplo, la paginación.
Una vez aprendida la teoría, ¡vamos a la práctica!
Creación de un Store
La creación de un Store es bastante sencillo, simplemente debemos ejecutar el siguiente comando:
ng add @ngrx/store@latest
Recordemos que ng add lo que hace es, a parte de instalar la librería, ejecutar una serie de scripts para facilitar la integración en el proyecto.
Este comando nos instalará la última versión estable de la librería @ngrx/store, la añade a nuestro package.json y actualiza el módulo principal de la aplicación para registrar la store.
Este comando puede recibir una serie de flags que puedes ver en la documentación oficial, quizás el flag más destacable sería — no-minimal:
ng add @ngrx/store@latest --no-minimal
Este comando ejecutará las mismas acciones que el comando anterior, pero además creará una estructura de carpetas y archivos con interfaces y funciones que actúan como placeholder. Esta sería la manera más recomendada de crear una store, pero para este curso no te recomiendo usarla si es tu primera app, una vez finalices el curso serás capaz de utilizar esta especie de plantilla para adaptarla a las necesidades de tu proyecto.
¿Cómo se interactúa con un Store?
En NgRx no podemos interactuar directamente con el estado, lo debemos hacer o bien mediante selectores o mediante acciones. Un Store se compone de los siguientes elementos:
- State: Imagina que tiene un mapa que define la información se almacenará en el Store, esto vendría siendo el State.
- Actions: Es la representación de las acciones o eventos que puede emitir un usuario.
- Reducers: Implementa la lógica para realizar la actualización del estado en función de la acción emitida.
- Selectors: Se encarga de la obtención parcial o total del estado.
Creo que la mejor manera de visualizar todo esto es mediante un ejemplo. Ojo el siguiente ejemplo contiene elementos que aún no profundizado lo suficiente como para entenderlo al detalle, pero no te preocupes si no lo terminas de entender, quiero que lo veas en su conjunto y no te enfoques tanto en la implementación, sino en cómo interactúan y se comunican cada una de estas partes. Profundizaremos en cada uno de estos elementos en su respectivo módulo, así ¡que no te preocupes!
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 un Store
Vamos a ver cómo implementaríamos un store en nuestra aplicación NgPartyPlanner, si aún no conoces esta aplicación te recomiendo leer su artículo, pero como resumen, es la aplicación que utilizaremos para aplicar todos nuestros conocimientos.
Para este ejercicio queremos hacer dos cosas:
- Visualizar a todos los invitados
- Cargar el listado de invitados
Como hemos visto, tenemos varias maneras de interactuar con el estado según nuestras necesidades, por lo que tendremos que utilizar tanto los selectores para obtener y visualizar el listado de invitados y las acciones con los reductores para cargar el listado.
El esquema que tendríamos que seguir sería más o menos el siguiente:
Clona la rama “init” de nuestro repositorio de github, es importante que lo hagas sobre esta rama debido a que vamos a partir de una base con varias librerías y componentes preparados, de esta forma podremos enfocarnos en la gestión del estado y no tanto de la visualización de estos datos.
Una vez clonado nuestro repositorio, debemos instalar las librerías que utilizaremos:
npm install
Si te fijas en el package.json no hay ninguna librería de ngrx, así que las iremos instalando conforme vamos explorando cada uno de los módulos, en este caso vamos a ejecutar el comando comentado en este artículo:
ng add @ngrx/store@latest
Primero, vamos a definir las acciones del usuario, que en este es solamente la carga del listado.
Crea un nuevo archivo en src/app/core/stores/guests con nombre guest.model.ts con el siguiente contenido:
export interface Guest {
id: string;
fullName: string;
email: string;
cellPhone: string;
picture: string;
isAttendeeConfirmed: boolean | null;
}
Vamos a definir la interfaz del estado de la aplicación, para ello, en la carpeta store crea un archivo app.state.ts
con una propiedad guests de tipo Array<Guest>:
import { Guest } from './guests/guest.model';
export interface AppState {
guests?: Array<Guest>;
}
Ahora debemos crear las acciones, para ello vamos a utilizar la función createAction de @ngrx/store (no te preocupes, abordaremos este tema en el siguiente módulo) y definimos la acción con sus parámetros.
Crea un nuevo archivo llamado guest.actions.ts
en el paquete guests. La acción loadGuests recibirá como parámetro un objeto (o payload) con la propiedad “guests” que es un Array de tipo Guest:
import { createAction, props } from '@ngrx/store';
import { Guest } from './guest.model';
export const loadGuests = createAction(
'[Guests] Load Guests',
props<{ guests: Array<Guest> }>()
);
Ahora crea el archivo guest.reducer.ts
, también en el mismo paquete. En este archivo vamos a definir los reductores que se ejecutarán al invocar una llamada. Para ello, vamos a utilizar los métodos createReducer
y on
, en este último definimos la función que se ejecutará cada vez que invoca la acción. Esta función devolverá el nuevo estado (repito, ya detallaremos el proceso en su respectivo módulo, ahora solo cierra los ojos y confía):
import { createReducer, on } from '@ngrx/store';
import * as guestsActions from './guest.actions';
import { Guest } from './guest.model';
const initialState: Array<Guest> = [];
export const guestsReducer = createReducer(
initialState,
on(
guestsActions.loadGuests,
(_state, { guests }): Array<Guest> => [...guests]
)
);
Es el turno de los reductores, para ello crea un el archivo guest.selectors.ts
en el mismo paquete. En él vamos a definir la función encargada de obtener el estado correspondiente (paciencia):
import { AppState } from '../app.state';
import { createSelector } from '@ngrx/store';
export const selectAllGuests = (state: AppState) => state.guests;
export const selectGuests = createSelector(selectAllGuests, (g) => g);
Por último, en el guests.component.ts
vamos a invocar a todo esto que hemos ido creando. El objeto store se inyecta en el constructor (línea 24) y en el ngOnInit
invocamos al método dispatch
, que recibe como parámetro una Acción, que a su vez esta acción recibe el parámetro anteriormente definido (líneas 26 a 57). En una aplicación real, seguramente el listado de invitados se obtendrían vía API, pero de momento vamos a mockearlo:
También es necesario modificar guests-dataview.component.ts, aquí ya no hace falta un input guests dado que este listado lo obtendremos directamente del store. Para ello volvemos a inyectarlo en el constructor, pero en este componente (de momento) solo necesitamos invocar al selector.
Recuerda que tienes disponible en el repositorio una rama con todo este código, por si quieres copiarlo 😉
Después de este ejemplo estoy seguro de que ya tienes claro qué es un store y qué función tiene cada una de sus partes (actions, reduces y selectors), pero… exactamente ¿Qué es un Action? ¿Cómo se crean? ¿Y los reducers? ¿Y los selectors?, pues bien, ¡lo veremos en los siguientes artículos!