Cómo crear un Store o State Management con RxJS en Angular (Parte 2/2)
Puedes encontrar la parte 1 de este post haciendo click aquí:
¿Dónde irá nuestro Store?
Luego de crear nuestro proyecto de angular vamos a crear una carpeta llamada core
para colocar todos nuestros servicios transversales, dentro del mismo crearemos la carpeta llamada services
luego store.
Arquitectura del Store
Muy inspirados en el patrón de redux
vamos a tener los siguientes conceptos:
Actions
Las actions como su traducción al español nos indica, contiene las acciones que queremos ejecutar en nuestro store, tales como ADD
REMOVE
UPDATE
, entre otros.
Dentro de la carpeta
store
crearemos un archivo llamadoactions.ts
el cual contendrá una constante llamadaactions
que listará las distintas acciones que podremos ejecutar en nuestro store.
AppState
Nuestro store
debe empezar con un estado inicial, es por ello que:
Dentro de la carpeta
store
creamos un archivo llamadoapp-state.ts
el cual contendrá el estado inicial de nuestro store. En mi caso haré un demo que trata sobre la creación de un curso, así que se ve más o menos así:
Es decir, que nuestro estado en el store estará compuesto por la lista de courses
que he creado y por el currentCourse
el cual usaré para el proceso de edición del mismo.
Reducers
Los reducers son los que contienen esa parte lógica que hará cada una de las acciones, aquí es donde debemos tener cuidado de aplicar la inmutabilidad. Ya que no podemos sobre escribir las variables.
Dentro de la carpeta
store
crearemos un archivo llamadoreducers.ts
el cual contendrá una función que evaluará a través de unswitch
la action a realizar y cada uno de los casos retornará el nuevo estado delstore.
Cómo podemos ver en este archivo añadimos las interfaces Action
y Reducer<T>
. El Action
es la interfaz que usaremos para armar el objeto que se recibirá en el reducer
.
Podemos notar en el código del reducer
que dicha función recibe lo siguiente: (state: any = {}, action: Action)
. en donde en el state
espera recibir el estado anterior del store, y en el action
se recibe el type
donde evaluará el tipo de la acción (ADD, REMOVE, etc) y finalmente el payload
el cual contiene el nuevo estado del store.
StoreService
El StoreService será el servicio que extiende de BehaviorSubject
para hacer los dispatcher
de los eventos o los nuevos estados del store. Básicamente no solo se encargará de recibir los nuevos estados si no a su vez de propagará los nuevos cambios los servicios facade
que estarán suscritos al store escuchando estos cambios.
A continuación crearemos un archivo llamada
store.service.ts
dentro de la carpetastore
con lo siguiente:
Cada vez que se hace un dispatch
o emisión de un nuevo cambio, este pasa por los reducers
los cuales se encargan de devolver un nuevo estado y posteriormente actual el store
. A continuación una visión general de esto:
Servicio Facade
Para tener una arquitectura más limpia, es recomendable que el store se mantenga un poco encapsulado en nuestro proyecto, y que a su vez si otros desarrolladores desean colaborar en el proyecto y hacer uso del store, lo hagan a través de un servicio llamada Facade
el cual se encarga de exponer las funcionalidades que nos brinda el store
sin necesidad de adentrarse mucho en la lógica que posee. Así mismo los componentes interesados en obtener la información del store, deberán consultarla directamente en el servicio Facade
y no en el store
, reduciendo posibles errores de implementación en el futuro.
Para el caso del demo, creé una carpeta de
services
en el módulo decourses
y allí creé elCoursesStoreService
el cual luce de la siguiente manera:
Como seguramente lo notaron, usé un operador custom en rxjs
llamado select
el cual se encarga de mapear el objeto del estado actual (si recuerdan contenía un parámetro llamado courses
y otro llamado currentCourse
) solo con el fin de hacer un poco más limpia la respuesta, y devolver solo lo que estoy consultando o deseo consultar.
Ya tenemos nuestro Store, now what?
Bueno, ahora simplemente debemos implementar las vistas para el demo.
Aunque no voy a detallar cómo se hicieron las vistas por que salen del scope del store
, sí les comentaré un poco sobre la estructura y arquitectura que recomiendo para hacer visualizar la data del store y a su vez ejecutar los cambios.
Estructura de las Carpetas
Si bien ya les comenté un poco sobre la estructura del store, para las vistas se ve un poco de la siguiente manera:
En donde en el dashboard, creé un módulo llamado courses
el cual contiene los componentes que usaré para el demo:
courses-list
muestra la lista de cursos.course-form
contiene un formulario con una serie de pasos para crear el curso.course-details
el cual solo muestra la información del curso seleccionado.
Suscripción al Store
Lo primero y principal es conocer el concepto de SmartComponents
y DumpComponents
en donde simplemente trata de tener un componente principal el cual se encargará de conectarse al CourseStoreService
y consultar el estado actual del curso que se esta creando y/o editando o currentCourse$
el cual será un observable que emitirá en realtime un solo curso determinado. Y los demás componentes, que se comportarán como “hijos” del principal, simplemente recibirán el estado actual de currentCourse$
como una variable entre componentes a través de lo que conocemos en angular como Input()
, ésta es simplemente una de las formas que propone angular de compartir información entre componentes, en el caso de componentes “padres” e “hijos” la comunicación más recomendada es a través de Inputs y Outputs.
Para mostrar la lista de cursos courses$
simplemente me suscribí a través del DOM usando el pipe async
que ofrece Angular, este pipe lo que nos permite es hacer la suscripción automática y en realtime de la lista de cursos solo cuando el usuario haya llegado a ésa vista. Así mismo use la directiva *ngFor
para iterar en la lista de cursos y mostrar una card
por cada elemento.
Recomendaciones
Async Pipe
Es muy bueno tomar en cuenta el uso del pipe async
que recién mencioné, por las siguientes razones:
- Se suscribe automáticamente al observable una vez el componente es inicializado.
- Muestra en realtime cada cambio realizado en la información que se esta mostrando en el observable.
- Se desubscribe o hace
unsubscribe
automáticamente cuando el componente se destruye, ahorrándonos ése paso y posibles memory-leaks en el futuro.
OnPush Change Detection Strategy
Este topic puede ser un poco largo a la hora de explicar, pero básicamente (haciendo un super resumen) la estrategia de la detección de cambios que trae angular por defecto se encuentra “escuchando” de todos los cambios que suceden en la aplicación en todos los componentes, inclusive en aquellos componentes en los que no necesitamos saber esos cambios, es por eso que a través de la configuración en el componente donde sí nos interese escuchar los cambios, debemos implementar el ChangeDetectionStrategy.OnPush como estrategia de changeDetection
, para que nuestra aplicación sólo esté escuchando los cambios en los componentes que declaramos y no en otros donde no sea necesario, mejorando considerablemente el performance de nuestra aplicación. Si te interesa saber un poco más sobre como funciona puedes ver este super post de Minko Gechev.
@Component({
selector: 'app-courses-list',
templateUrl: './courses-list.component.html',
styleUrls: ['./courses-list.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class CoursesListComponent implements OnInit {
Demo
Puedes ver el demo corriendo en el siguiente link:
https://ng-rxjs-store-demo.firebaseapp.com
y por supuesto ver el código completo aquí:
Espero que te haya gustado este post que escribí con mucho cariño ❤
Muchas gracias!