Una inmersión profunda en las ventajas y características de NgRx
Publicado: 2022-03-11Si un líder de equipo le indica a un desarrollador que escriba una gran cantidad de código repetitivo en lugar de escribir algunos métodos para resolver un problema determinado, necesita argumentos convincentes. Los ingenieros de software son solucionadores de problemas; prefieren automatizar las cosas y evitar repeticiones innecesarias.
Aunque NgRx viene con un código repetitivo, también proporciona herramientas poderosas para el desarrollo. Este artículo demuestra que dedicar un poco más de tiempo a escribir código generará beneficios que harán que el esfuerzo valga la pena.
La mayoría de los desarrolladores comenzaron a utilizar la gestión del estado cuando Dan Abramov lanzó la biblioteca Redux. Algunos comenzaron a utilizar la gestión estatal porque era una tendencia, no porque carecieran de ella. Los desarrolladores que utilizan un proyecto estándar "Hello World" para la administración del estado podrían encontrarse rápidamente escribiendo el mismo código una y otra vez, aumentando la complejidad sin obtener ganancias.
Con el tiempo, algunos se sintieron frustrados y abandonaron por completo la gestión estatal.
Mi problema inicial con NgRx
Creo que esta preocupación repetitiva fue un problema importante con NgRx. Al principio, no pudimos ver el panorama general detrás de esto. NgRx es una biblioteca, no un paradigma de programación o una mentalidad. Sin embargo, para comprender completamente la funcionalidad y la usabilidad de esta biblioteca, tenemos que ampliar nuestro conocimiento un poco más y centrarnos en la programación funcional. Ahí es cuando puede llegar a escribir código repetitivo y sentirse feliz por ello. (Lo digo en serio). Una vez fui un escéptico de NgRx; ahora soy un admirador de NgRx.
Hace un tiempo, comencé a usar la gestión estatal. Pasé por la experiencia repetitiva descrita anteriormente, así que decidí dejar de usar la biblioteca. Como amo JavaScript, trato de obtener al menos un conocimiento fundamental sobre todos los marcos de trabajo populares que se usan hoy en día. Esto es lo que aprendí mientras usaba React.
React tiene una función llamada Hooks. Al igual que los Componentes en Angular, los Hooks son funciones simples que aceptan argumentos y devuelven valores. Un gancho puede tener un estado en él, que se llama efecto secundario. Entonces, por ejemplo, un botón simple en Angular podría traducirse a Reaccionar así:
@Component({ selector: 'simple-button', template: ` <button>Hello {{ name }}</button> `, }) export class SimpleButtonComponent { @Input() name!: string; } export default function SimpleButton(props: { name: string }) { return <button>{props.name} </button>; }Como puede ver, esta es una transformación sencilla:
- ComponenteBotónSimple => BotónSimple
- @Input() nombre => accesorios.nombre
- plantilla => valor de retorno
Nuestra función React SimpleButton tiene una característica importante en el mundo de la programación funcional: es una función pura . Si estás leyendo esto, supongo que habrás escuchado ese término al menos una vez. NgRx.io cita funciones puras dos veces en los conceptos clave:
- Los cambios de estado son manejados por funciones puras llamadas
reducersque toman el estado actual y la última acción para calcular un nuevo estado. - Los selectores son funciones puras que se utilizan para seleccionar, derivar y componer piezas de estado.
En React, se alienta a los desarrolladores a usar Hooks como funciones puras tanto como sea posible. Angular también alienta a los desarrolladores a implementar el mismo patrón usando el paradigma Smart-Dumb Component.
Fue entonces cuando me di cuenta de que me faltaban algunas habilidades cruciales de programación funcional. No me tomó mucho tiempo comprender NgRx, ya que después de aprender los conceptos clave de la programación funcional, tuve un "¡Ajá! momento”: había mejorado mi comprensión de NgRx y quería usarlo más para comprender mejor los beneficios que ofrece.
Este artículo comparte mi experiencia de aprendizaje y el conocimiento que obtuve sobre NgRx y la programación funcional. No explico la API para NgRx o cómo llamar a acciones o usar selectores. En cambio, comparto por qué he llegado a apreciar que NgRx es una gran biblioteca: no es solo una tendencia relativamente nueva, sino que brinda una gran cantidad de beneficios.
Comencemos con la programación funcional .
Programación funcional
La programación funcional es un paradigma que difiere mucho de otros paradigmas. Este es un tema muy complejo con muchas definiciones y pautas. Sin embargo, la programación funcional contiene algunos conceptos básicos y conocerlos es un requisito previo para dominar NgRx (y JavaScript en general).
Estos conceptos básicos son:
- función pura
- Estado inmutable
- Efecto secundario
Repito: es solo un paradigma, nada más. No hay una biblioteca funcional.js que descarguemos y usemos para escribir software funcional. Es solo una forma de pensar en escribir aplicaciones. Comencemos con el concepto central más importante: función pura .
función pura
Una función se considera una función pura si sigue dos reglas simples:
- Pasar los mismos argumentos siempre devuelve el mismo valor
- Careciendo de un efecto secundario observable involucrado dentro de la ejecución de la función (un cambio de estado externo, llamando a la operación de E/S, etc.)
Entonces, una función pura es solo una función transparente que acepta algunos argumentos (o ningún argumento) y devuelve un valor esperado. Puede estar seguro de que llamar a esta función no tendrá efectos secundarios, como la conexión en red o el cambio de algún estado de usuario global.
Veamos tres ejemplos simples:
//Pure function function add(a,b){ return a + b; } //Impure function breaking rule 1 function random(){ return Math.random(); } //Impure function breaking rule 2 function sayHello(name){ console.log("Hello " + name); }- La primera función es pura porque siempre devolverá la misma respuesta al pasar los mismos argumentos.
- La segunda función no es pura porque no es determinista y devuelve diferentes respuestas cada vez que se llama.
- La tercera función no es pura porque usa un efecto secundario (llamar a
console.log).
Es fácil discernir si la función es pura o no. ¿Por qué una función pura es mejor que una impura? Porque es más fácil de pensar. Imagina que estás leyendo un código fuente y ves una llamada de función que sabes que es pura. Si el nombre de la función es correcto, no necesita explorarlo; sabes que no cambia nada, te devuelve lo que esperas. Es crucial para la depuración cuando tiene una aplicación empresarial enorme con mucha lógica comercial, ya que puede ahorrar mucho tiempo.
Además, es fácil de probar. No tiene que inyectar nada ni burlarse de algunas funciones dentro de él, solo pasa argumentos y prueba si el resultado es una coincidencia. Existe una fuerte conexión entre la prueba y la lógica: si un componente es fácil de probar, es fácil entender cómo y por qué funciona.
Las funciones puras vienen con una funcionalidad muy práctica y fácil de usar llamada memorización. Si sabemos que llamar a los mismos argumentos devolverá el mismo valor, simplemente podemos almacenar en caché los resultados y no perder el tiempo llamándolo de nuevo. NgRx definitivamente se sienta encima de la memorización; esa es una de las principales razones por las que es rápido.
Puede preguntarse: “¿Qué pasa con los efectos secundarios? ¿A dónde van?" En su charla GOTO, Russ Olsen bromea diciendo que nuestros clientes no nos pagan por funciones puras, nos pagan por efectos secundarios. Eso es cierto: a nadie le importa la función pura de Calculadora si no se imprime en alguna parte. Los efectos secundarios tienen su lugar en el universo de la programación funcional. Eso lo veremos en breve.
Por ahora, pasemos al siguiente paso en el mantenimiento de arquitecturas de aplicaciones complejas, el siguiente concepto central: estado inmutable .
Estado inmutable
Hay una definición simple para un estado inmutable:
- Solo puede crear o eliminar un estado. No puedes actualizarlo.
En términos simples, para actualizar la edad de un objeto Usuario...:
let user = { username:"admin", age:28 }… deberías escribirlo así:
// Not like this newUser.age = 30; // But like this let newUser = {...user, age:29 }Cada cambio es un nuevo objeto que ha copiado propiedades de los antiguos. Como tal, ya estamos en una forma de estado inmutable.
String, Boolean y Number son estados inmutables: no puede agregar ni modificar valores existentes. Por el contrario, una fecha es un objeto mutable: siempre manipula el mismo objeto de fecha.
La inmutabilidad se aplica en toda la aplicación: si pasa un objeto de usuario dentro de la función que cambia su edad, no debería cambiar un objeto de usuario, debería crear un nuevo objeto de usuario con una edad actualizada y devolverlo:
function updateAge(user, age) { return {...user, age: age) } let user = {username: 'admin', age: 29}; let newUser = updateAge(user, 32);¿Por qué debemos dedicar tiempo y atención a esto? Hay un par de beneficios que vale la pena subrayar.
Un beneficio para los lenguajes de programación de back-end implica el procesamiento paralelo. Si un cambio de estado no depende de una referencia y cada actualización es un objeto nuevo, puede dividir el proceso en partes y trabajar en la misma tarea con innumerables subprocesos sin compartir la misma memoria. Incluso puede paralelizar tareas entre servidores.
Para marcos como Angular y React, el procesamiento paralelo es una de las formas más beneficiosas de mejorar el rendimiento de la aplicación. Por ejemplo, Angular tiene que verificar las propiedades de cada objeto que pasa a través de los enlaces de entrada para discernir si un componente debe volver a renderizarse o no. Pero si configuramos ChangeDetectionStrategy.OnPush en lugar del predeterminado, verificará por referencia y no por cada propiedad. En una aplicación grande, esto definitivamente ahorra tiempo. Si actualizamos nuestro estado de forma inmutable, obtenemos este aumento de rendimiento de forma gratuita.
El otro beneficio de un estado inmutable que comparten todos los marcos y lenguajes de programación es similar a los beneficios de las funciones puras: es más fácil pensar y probar. Cuando un cambio es un nuevo estado que nace de uno anterior, sabe exactamente en qué está trabajando y puede rastrear exactamente cómo y dónde cambió el estado. No pierde el historial de actualizaciones y puede deshacer/rehacer cambios para el estado (React DevTools es un ejemplo).
Sin embargo, si se actualiza un solo estado, no conocerá el historial de esos cambios. Piense en un estado inmutable como el historial de transacciones de una cuenta bancaria. Es prácticamente un imprescindible.
Ahora que hemos revisado la inmutabilidad y la pureza, abordemos el concepto central restante: efecto secundario .
Efecto secundario
Podemos generalizar la definición de un efecto secundario:
- En informática, se dice que una operación, función o expresión tiene un efecto secundario si modifica algunos valores de variables de estado fuera de su entorno local. Es decir tiene un efecto observable además de devolver un valor (el efecto principal) al invocador de la operación.
En pocas palabras, todo lo que cambia un estado fuera del alcance de la función (todas las operaciones de E/S y algún trabajo que no está conectado directamente con la función) puede considerarse un efecto secundario. Sin embargo, debemos evitar el uso de efectos secundarios dentro de funciones puras porque los efectos secundarios contradicen la filosofía de programación funcional. Si usa una operación de E/S dentro de una función pura, deja de ser una función pura.
Sin embargo, necesitamos tener efectos secundarios en alguna parte, ya que una aplicación sin ellos no tendría sentido. En Angular, no solo las funciones puras deben protegerse contra los efectos secundarios, también debemos evitar usarlas en Componentes y Directivas.
Examinemos cómo podemos implementar la belleza de esta técnica dentro del marco Angular.
Programación Angular Funcional
Una de las primeras cosas que debe comprender sobre Angular es la necesidad de desacoplar los componentes en componentes más pequeños con la mayor frecuencia posible para facilitar el mantenimiento y las pruebas. Esto es necesario, ya que necesitamos dividir nuestra lógica de negocios. Además, se alienta a los desarrolladores de Angular a que dejen los componentes solo con fines de representación y muevan toda la lógica comercial dentro de los servicios.
Para ampliar estos conceptos, los usuarios de Angular agregaron el patrón "Componente tonto-inteligente" a su vocabulario. Este patrón requiere que las llamadas de servicio no existan dentro de los componentes pequeños. Debido a que la lógica comercial reside en los servicios, todavía tenemos que llamar a estos métodos de servicio, esperar su respuesta y solo entonces realizar cambios de estado. Entonces, los componentes tienen alguna lógica de comportamiento dentro de ellos.
Para evitar eso, podemos crear un componente inteligente (componente raíz), que contiene lógica comercial y de comportamiento, pasar los estados a través de las propiedades de entrada y llamar a acciones escuchando los parámetros de salida. De esta manera, los componentes pequeños son realmente solo para propósitos de renderizado. Por supuesto, nuestro componente raíz debe tener algunas llamadas de servicio dentro y no podemos simplemente eliminarlas, sino que su utilidad se limitaría solo a la lógica comercial, no a la representación.
Veamos un ejemplo de componente de contador. Un contador es un componente que tiene dos botones que aumentan o disminuyen el valor y un displayField que muestra el currentValue . Así que terminamos con cuatro componentes:
- ContadorContenedor
- AumentarBoton
- Botón de disminución
- Valor actual
Toda la lógica vive dentro del CounterContainer , por lo que los tres son solo renderizadores. Aquí está el código para los tres:
@Component({ selector: 'decrease-button', template: `<button (click)="increase.emit()" [disabled]="disabled"> Decrease </button>`, }) export class DecreaseButtonComponent { @Input() disabled!: boolean; @Output() increase = new EventEmitter(); } @Component({ selector: 'current-value', template: `<button> {{ currentValue }} </button>`, }) export class CurrentValueComponent { @Input() currentValue!: string; } @Component({ selector: 'increase-button', template: `<button (click)="increase.emit()" [disabled]="disabled"> Increase </button>`, }) export class IncreaseButtonComponent { @Input() disabled!: boolean; @Output() increase = new EventEmitter(); }Mira lo simples y puros que son. No tienen efectos secundarios ni de estado, solo dependen de las propiedades de entrada y de los eventos de emisión. Imagina lo fácil que es probarlos. Podemos llamarlos componentes puros porque eso es lo que realmente son. Dependen solo de los parámetros de entrada, no tienen efectos secundarios y siempre devuelven el mismo valor (cadena de plantilla) pasando los mismos parámetros.

Entonces, las funciones puras en la programación funcional se transfieren a los componentes puros en Angular. Pero, ¿dónde va toda la lógica? La lógica sigue ahí, pero en un lugar ligeramente diferente, a saber, el CounterComponent .
@Component({ selector: 'counter-container', template: ` <decrease-button [disabled]="decreaseIsDisabled" (decrease)="decrease()"> </decrease-button> <current-value [currentValue]="currentValue"> </current-value> <increase-button (increase)="increase()" [disabled]="increaseIsDisabled"> </increase-button> `, }) export class CounterContainerComponent implements OnInit { @Input() disabled!: boolean; currentValue = 0; get decreaseIsDisabled() { return this.currentValue === 0; } get increaseIsDisabled() { return this.currentValue === 100; } constructor() {} ngOnInit(): void {} decrease() { this.currentValue -= 1; } increase() { this.currentValue += 1; } } Como puede ver, la lógica de comportamiento vive en CounterContainer pero falta la parte de representación (declara componentes dentro de la plantilla) porque la parte de representación es para componentes puros.
Podríamos inyectar todo el servicio que queramos porque manejamos todas las manipulaciones de datos y cambios de estado aquí. Una cosa que vale la pena mencionar es que si tenemos un componente anidado profundo, no debemos crear solo un componente de nivel raíz. Podemos dividirlo en componentes inteligentes más pequeños y usar el mismo patrón. En última instancia, depende de la complejidad y el nivel anidado de cada componente.
Podemos pasar fácilmente de ese patrón a la biblioteca NgRx, que está solo una capa por encima.
Biblioteca NgRx
Podemos dividir cualquier aplicación web en tres partes principales:
- Lógica de negocios
- Estado de la aplicación
- Lógica de representación
La lógica empresarial es todo el comportamiento que le sucede a la aplicación, como redes, entrada, salida, API, etc.
Estado de la aplicación es el estado de la aplicación. Puede ser global, como el usuario autorizado actualmente, y también local, como el valor actual del componente de contador.
Rendering Logic abarca el renderizado, como la visualización de datos mediante DOM, la creación o eliminación de elementos, etc.
Al usar el patrón Dumb-Smart, desvinculamos la lógica de representación de la lógica comercial y el estado de la aplicación, pero también podemos dividirlos porque ambos son conceptualmente diferentes entre sí. El estado de la aplicación es como una instantánea de su aplicación en el momento actual. Business Logic es como una funcionalidad estática que siempre está presente en su aplicación. La razón más importante para dividirlos es que Business Logic es principalmente un efecto secundario que queremos evitar en el código de la aplicación tanto como sea posible. Aquí es cuando brilla la biblioteca NgRx, con su paradigma funcional.
Con NgRx desacoplas todas estas partes. Hay tres partes principales:
- reductores
- Comportamiento
- Selectores
Combinados con la programación funcional, los tres se combinan para brindarnos una poderosa herramienta para manejar aplicaciones de cualquier tamaño. Examinemos cada uno de ellos.
reductores
Un reductor es una función pura, que tiene una firma simple. Toma un estado anterior como parámetro y devuelve un estado nuevo, ya sea derivado del anterior o de uno nuevo. El estado en sí es un solo objeto, que vive con el ciclo de vida de su aplicación. Es como una etiqueta HTML, un único objeto raíz.
No puede modificar directamente un objeto de estado, debe modificarlo con reductores. Eso tiene una serie de beneficios:
- La lógica de cambio de estado vive en un solo lugar, y usted sabe dónde y cómo cambia el estado.
- Las funciones del reductor son funciones puras, que son fáciles de probar y administrar.
- Debido a que los reductores son funciones puras, se pueden memorizar, lo que permite almacenarlos en caché y evitar cálculos adicionales.
- Los cambios de estado son inmutables. Nunca actualizas la misma instancia. En cambio, siempre devuelves uno nuevo. Esto permite una experiencia de depuración de "viaje en el tiempo".
Este es un ejemplo trivial de un reductor:
function usernameReducer(oldState, username) { return {...oldState, username} }Aunque es un reductor ficticio muy simple, es el esqueleto de todos los reductores largos y complejos. Todos comparten los mismos beneficios. Podríamos tener cientos de reductores en nuestra aplicación y podemos hacer tantos como queramos.
Para nuestro componente de contador, nuestro estado y reductores podrían verse así:
interface State{ decreaseDisabled:boolean; increaseDisabled:boolean; currentValue:number; } const MIN_VALUE=0; const MAX_VALUE =100; function decreaseReducer(oldState) { const newValue = oldState.currentValue -1 return {...oldState,currentValue : newValue, decreaseDisabled: newValue===MIN_VALUE } function increaseReducer(oldState) { const newValue = oldState.currentValue + 1 return {...oldState,currentValue : newValue, decreaseDisabled: newValue===MAX_VALUE }Eliminamos el estado del componente. Ahora necesitamos una forma de actualizar nuestro estado y llamar al reductor apropiado. Ahí es cuando las acciones entran en juego.
Comportamiento
Una acción es una forma de notificar a NgRx para llamar a un reductor y actualizar el estado. Sin eso, no tendría sentido usar NgRx. Una acción es un objeto simple que adjuntamos al reductor actual. Después de llamarlo, se llamará al reductor apropiado, por lo que en nuestro ejemplo podríamos tener las siguientes acciones:
enum CounterActions { IncreaseValue = '[Counter Component] Increase Value', DecreaseValue = '[Counter Component] Decrease Value', } on(CounterActions.IncreaseValue,increaseReducer); on(CounterActions.DecreaseValue,decreaseReducer);Nuestras acciones están unidas a los reductores. Ahora podemos modificar aún más nuestro Componente Contenedor y llamar a las acciones apropiadas cuando sea necesario:
@Component({ selector: 'counter-container', template: ` <decrease-button [disabled]="decreaseIsDisabled" (decrease)="decrease()"> </decrease-button> <current-value [currentValue]="currentValue"> </current-value> <increase-button (increase)="increase()" [disabled]="increaseIsDisabled"> </increase-button> `, }) export class CounterContainerComponent implements OnInit { constructor(private store: Store<any>) {} decrease() { this.store.dispatch(CounterActions.DicreaseValue); } increase() { this.store.dispatch(CounterActions.IncreaseValue); } }Nota: eliminamos el estado y lo volveremos a agregar en breve .
Ahora nuestro CounterContainer no tiene ninguna lógica de cambio de estado. Simplemente sabe qué enviar. Ahora necesitamos alguna forma de mostrar estos datos a la vista. Esa es la utilidad de los selectores.
Selectores
Un selector también es una función pura muy simple, pero a diferencia de un reductor, no actualiza el estado. Como su nombre lo indica, el selector simplemente lo selecciona. En nuestro ejemplo, podríamos tener tres selectores simples:
function selectCurrentValue(state) { return state.currentValue; } function selectDicreaseIsDisabled(state) { return state.decreaseDisabled; } function selectIncreaseIsDisabled(state) { return state.increaseDisabled; } Usando estos selectores, podríamos seleccionar cada porción de un estado dentro de nuestro componente inteligente CounterContainer .
@Component({ selector: 'counter-container', template: ` <decrease-button [disabled]="ecreaseIsDisabled$ | async" (decrease)="decrease()" > </decrease-button> <current-value [currentValue]="currentValue$ | async"> </current-value> <increase-button (increase)="increase()" [disabled]="increaseIsDisabled$ | async" > </increase-button> `, }) export class CounterContainerComponent implements OnInit { decreaseIsDisabled$ = this.store.select(selectDicreaseIsDisabled); increaseIsDisabled$ = this.store.select(selectIncreaseIsDisabled); currentValue$ = this.store.select(selectCurrentValue); constructor(private store: Store<any>) {} decrease() { this.store.dispatch(CounterActions.DicreaseValue); } increase() { this.store.dispatch(CounterActions.IncreaseValue); } }Estas selecciones son asincrónicas por defecto (al igual que los Observables en general). Esto no tiene importancia, al menos desde el punto de vista del patrón. Lo mismo sería cierto para uno síncrono, ya que simplemente seleccionaríamos algo de nuestro estado.
Demos un paso atrás y miremos el panorama general para ver lo que hemos logrado hasta ahora. Tenemos una aplicación de contador, que tiene tres partes principales que están casi desacopladas entre sí. Nadie sabe cómo se administra el estado de la aplicación o cómo la capa de representación representa el estado.
Las partes desacopladas usan el puente (Acciones, Selectores) para conectarse entre sí. Están desacoplados hasta tal punto que podríamos tomar todo el código de la aplicación estatal y moverlo a otro proyecto, como por ejemplo para una versión móvil. Lo único que tendríamos que implementar sería el renderizado. Pero ¿qué pasa con las pruebas?
En mi humilde opinión, las pruebas son la mejor parte de NgRx. Probar este proyecto de muestra es similar a jugar tres en raya. Solo hay funciones puras y componentes puros, por lo que probarlos es pan comido. Ahora imagina si este proyecto se vuelve más grande, con cientos de componentes. Si seguimos el mismo patrón, simplemente agregaríamos más y más piezas juntas. No se convertiría en una mancha desordenada e ilegible de código fuente.
Ya casi hemos terminado. Solo queda una cosa importante por cubrir: los efectos secundarios. Mencioné los efectos secundarios muchas veces hasta ahora, pero no llegué a explicar dónde almacenarlos.
Esto se debe a que los efectos secundarios son la guinda del pastel y, al crear este patrón, es muy fácil eliminarlos del código de la aplicación.
Efectos secundarios
Digamos que nuestra aplicación de contador tiene un temporizador y cada tres segundos aumenta automáticamente el valor en uno. Este es un efecto secundario simple, que tiene que vivir en alguna parte. Es el mismo efecto secundario, por definición, que una solicitud de Ajax.
Si pensamos en los efectos secundarios, la mayoría tiene dos razones principales para existir:
- Hacer cualquier cosa fuera del entorno estatal.
- Actualización del estado de la aplicación
Por ejemplo, almacenar algún estado dentro de LocalStorage es la primera opción, mientras que actualizar el estado desde la respuesta de Ajax es la segunda. Pero ambos comparten la misma firma: cada efecto secundario debe tener algún punto de partida. Debe llamarse al menos una vez para que inicie la acción.
Como describimos anteriormente, NgRx tiene una buena herramienta para darle un comando a alguien. Eso es una acción. Podríamos llamar a cualquier efecto secundario despachando una acción. El pseudocódigo podría verse así:
function startTimer(){ setInterval(()=>{ console.log("Hello application"); },3000) } on(CounterActions.StartTime,startTimer) ... // We start timer by dispatching an action dispatch(CounterActions.StartTime);Es bastante trivial. Como mencioné anteriormente, los efectos secundarios actualizan algo o no. Si un efecto secundario no actualiza nada, no hay nada que hacer; simplemente lo dejamos. Pero si queremos actualizar un estado, ¿cómo lo hacemos? De la misma manera que un componente intenta actualizar un estado: llamando a otra acción. Entonces llamamos a una acción dentro del efecto secundario, que actualiza el estado:
function startTimer(store) { setInterval(()=> { // We are dispatching another action dispatch(CounterActions.IncreaseValue) }, 3000) } on(CounterActions.StartTime, startTimer); ... // We start timer by dispatching an action dispatch(CounterActions.StartTime);Ahora tenemos una aplicación totalmente funcional.
Resumiendo nuestra experiencia NgRx
Hay algunos temas importantes que me gustaría mencionar antes de que terminemos nuestro viaje con NgRx:
- El código que se muestra es un pseudocódigo simple que inventé para el artículo; solo es apto para fines de demostración. NgRx es el lugar donde viven las fuentes reales.
- No existe una directriz oficial que pruebe mi teoría sobre la conexión de la programación funcional con la biblioteca NgRx. Es solo mi opinión formada después de leer docenas de artículos y ejemplos de código fuente creados por personas altamente capacitadas.
- Después de usar NgRx, definitivamente se dará cuenta de que es mucho más complejo que este simple ejemplo. Mi objetivo no era hacer que pareciera más simple de lo que realmente es, sino mostrarte que, aunque es un poco complejo e incluso puede resultar en un camino más largo hacia el destino, vale la pena el esfuerzo adicional.
- El peor uso de NgRx es usarlo en todas partes, independientemente del tamaño o la complejidad de la aplicación. Hay algunos casos en los que no debes usar NgRx; por ejemplo, en formularios. Es casi imposible implementar formularios dentro de NgRx. Los formularios están pegados al propio DOM; no pueden vivir separados. Si intenta desacoplarlos, se encontrará odiando no solo NgRx sino también la tecnología web en general.
- A veces, usar el mismo código repetitivo, incluso para un pequeño ejemplo, puede convertirse en una pesadilla, incluso si puede beneficiarnos en el futuro. Si ese es el caso, simplemente integre con otra biblioteca increíble, que es parte del ecosistema NgRx (ComponentStore).
