De forma similar a las acciones, NgRx proporciona una función de utilidad llamada createReducer
para crear reductores. Una típica llamada a la función createReducer
sería como la siguiente.
Como puedes ver, toma el estado inicial (el estado al inicio de la aplicación) y funciones de cambio de estado de uno a varios que definen cómo reaccionar a diferentes acciones. Cada una de estas funciones de cambio de estado recibe el estado actual y la acción como parámetros, y devuelve un nuevo estado.
Efectos
Los efectos permiten realizar efectos secundarios cuando se envía una acción al almacén. Vamos a tratar de entender esto a través de un ejemplo. Cuando un usuario se registra con éxito en una aplicación, una acción con type
Login Action
será enviada al almacén con la información del usuario en el payload
. Una función reductora escuchará esta acción y modificará el estado con la información del usuario. Además, como efecto secundario, también se quiere guardar la información del usuario en el almacenamiento local del navegador. Un efecto puede ser utilizado para llevar a cabo esta tarea adicional (efecto secundario).
Hay múltiples maneras de crear efectos en NgRx. A continuación se presenta una forma cruda y autoexplicativa de crear efectos. Tenga en cuenta que generalmente no se utiliza este método para crear efectos. Sólo tomé esto como un ejemplo para explicar lo que sucede detrás de la cortina.
actions$
observable emitirá acciones recibidas por la tienda. Estos valores pasarán por una cadena de operadores.
ofType
es el primer operador utilizado. Este es un operador especial proporcionado por NgRx (No RxJS) para filtrar las acciones basadas en su tipo. En este caso, sólo las acciones de tipo login
podrán pasar por el resto de la cadena de operadores.
tap
es el segundo operador utilizado en la cadena para almacenar la información del usuario en el almacenamiento local del navegador. El operador tap
se utiliza generalmente para realizar efectos secundarios en una cadena de operadores.
Por último, tenemos que suscribirnos manualmente al observable login$
.
Sin embargo, este enfoque tiene un par de inconvenientes importantes.
Tienes que suscribirte manualmente al observable, lo que no es una buena práctica. De esta manera, siempre tendrás que desuscribirte manualmente, lo que conduce a la falta de mantenibilidad.
Si aparece un error en la cadena de operadores, el observable se equivocará y dejará de emitir valores posteriores (acciones). Como resultado, el efecto secundario no se realizará. Por lo tanto, usted tiene que tener un mecanismo en el lugar para crear manualmente una nueva instancia observable y volver a suscribir si se produce un error.
Con el fin de superar estos problemas, NgRx proporciona una función de utilidad llamada createEffect
para crear efectos. Una llamada típica a la función createEffect
tendría el siguiente aspecto.
El método createEffect
recibe una función que devuelve un observable y (opcionalmente) un objeto de configuración como parámetros.
NgRx se encarga de la suscripción al observable devuelto por la función de soporte, y por lo tanto no hay que suscribirse o desuscribirse manualmente. Además, si se produce algún error en la cadena del operador, NgRx creará un nuevo observable y volverá a suscribirse para garantizar que el efecto secundario siempre se ejecute.
Si dispatch
es true
(valor por defecto) en el objeto de configuración, el método createEffect
devuelve un Observable<Action>
. En caso contrario, devuelve un Observable<Unknown>
. Si la propiedad dispatch
es true
, NgRx se suscribirá al observable devuelto de type
Observable<Action>
y enviará las acciones recibidas al almacén.
Si no está mapeando la acción recibida a un tipo de acción diferente en la cadena de operadores, tendrá que establecer dispatch
a false
. De lo contrario, la ejecución resultará en un bucle infinito, ya que la misma acción será despachada y recibida en el flujo actions$
una y otra vez. Por ejemplo, usted no tiene que establecer dispatch
a false
en el código de abajo porque se asigna la acción original a un tipo diferente de acción en la cadena de operador.
En el escenario anterior,
Efecto recibe acciones de type
loadAllCourses
.
Se invoca una API y se cargan cursos como efecto secundario.
Se mapea la respuesta de la API a una acción de type
allCoursesLoaded
y se pasan los cursos cargados como el payload
a la acción.
Y finalmente, la acciónallCoursesLoaded
que se ha creado se envía al almacén. Esto lo hace NgRx bajo el capó.
Un reductor escuchará la acción allCoursesLoaded
entrante y modificará el estado con los cursos cargados.
Selectores
Los selectores son funciones puras que se utilizan para obtener trozos del estado del almacén. Como se muestra a continuación, se puede consultar el estado incluso sin usar selectores. Pero este enfoque viene de nuevo con un par de contras importantes.
const isLoggedIn$ = this.store.pipe(map(state => !!state.user));
store
es un observable al que te puedes suscribir. Cada vez que el almacén recibe una acción, store
empujará el objeto de estado a sus suscriptores.
Puedes utilizar funciones de mapeo para obtener rebanadas de estado y llevar a cabo cualquier cálculo si es necesario. En el ejemplo anterior, estamos obteniendo el slice user
en el árbol de objetos de estado y convirtiéndolo en un booleano para determinar si el usuario ha iniciado sesión o no.
Puedes suscribirte manualmente al observable isLoggedIn$
o utilizarlo en una plantilla de Angular con async pipe para leer los valores emitidos.
Sin embargo, este enfoque tiene un inconveniente importante. En general, el almacén recibe acciones con frecuencia desde diferentes partes de la aplicación. Según la implementación anterior, cada vez que el almacén recibe una acción, un objeto de estado será emitido por el almacén. Y este objeto de estado pasará de nuevo por la función de mapeo y actualizará la UI.
Sin embargo, si el resultado de la función de mapeo no ha cambiado desde la última vez, no hay necesidad de actualizar la UI de nuevo. Por ejemplo, si el resultado de la map(state => !!state.user)
no ha cambiado desde la última ejecución, no tenemos que volver a empujar el resultado a la UI/Suscriptor. Para lograr esto, NgRx (no RxJS) ha introducido un operador especial llamado select
. Con el operador select
, el código anterior cambiará de la siguiente manera.
const isLoggedIn$ = this.store.pipe(select(state => !!state.user));
El operador select
evitará que los valores sean empujados a la UI/suscriptores si el resultado de la función de mapeo no ha cambiado desde la última vez.
Este enfoque puede ser mejorado aún más. Incluso si el operador select
no empuja los valores sin cambios a UI / suscriptores, todavía tiene que tomar el objeto de estado y hacer el cálculo para derivar el resultado cada vez.
Como ya se ha explicado anteriormente, un state
será emitido por el observable cuando el almacén recibe una acción de la aplicación. Una acción no siempre actualiza el estado. Si el estado no ha cambiado, el resultado del cálculo de la función de mapeo tampoco cambiará. Por lo tanto, no tenemos que hacer el cálculo de nuevo si el objeto state
emitido no ha cambiado desde la última vez. Aquí es donde entran en juego los selectores.
Un selector es una función pura que mantiene una memoria de las ejecuciones anteriores. Mientras la entrada no haya cambiado, la salida no será recalculada. En su lugar, la salida será devuelta desde la memoria. Este proceso se llama memoización.
NgRx proporciona una función de utilidad llamada createSelector
para construir selectores con capacidad de memoización. A continuación se muestra un ejemplo de la función de utilidad createSelector
.
La función createSelector
toma funciones de mapeo de uno a muchos que dan diferentes rebanadas del estado y una función de proyector que lleva a cabo el cálculo. La función del proyector no será invocada si las rebanadas de estado no han cambiado desde la última ejecución. Para utilizar la función selectora creada, hay que pasarla como argumento al operador select
.
this.isLoggedIn$ = this.store .pipe( select(isLoggedIn) );