Cómo crear un Store o State Management con RxJS en Angular (Parte 2/2)

Mayra Rodriguez
6 min readJan 21, 2020

--

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 llamado actions.ts el cual contendrá una constante llamada actions 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 llamado app-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 llamado reducers.ts el cual contendrá una función que evaluará a través de un switch la action a realizar y cada uno de los casos retornará el nuevo estado del store.

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:

Proceso desde el dispatch hasta la actualización del store.

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 de courses y allí creé el CoursesStoreService 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:

Ejemplo de las estructuras de carpetas para el demo

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.
Ejemplo de las estructuras de carpetas del course form para el demo

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

Ejemplo del 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!

--

--

Mayra Rodriguez

Google Developer Expert for Angular — Senior FullStack Engineer at Terminal Labs