Cómo realizar múltiples operaciones/efectos/acciones relacionadas con ngrx / effects
Estoy trabajando en una aplicación que está utilizando ngrx/store 1.5 junto con thunk middleware y estoy intentando pasar a ngrx/store 2.0 y ngrx/effects. Tengo un par de preguntas con respecto a cómo manejar múltiples acciones y/o efectos.
Me doy cuenta de que la "mentalidad" para los thunks vs efectos es diferente y estoy tratando de entender las diferencias. He mirado a través de las aplicaciones de ejemplo disponibles, y no he encontrado nada que parezca encajar con lo que soy intentándolo, así que tal vez todavía me estoy acercando completamente mal.
Hipótesis 1
Aquí hay un efecto secundario para manejar hacer la solicitud al servidor para un inicio de sesión:
@Effect login$: any = this.updates$
.whenAction(LoginActions.LOGIN)
.map(toPayload)
.switchMap(payload =>
this.loginService.login(payload.user, payload.password)
.map(result => this.actions.loginSuccess(value))
.catch((error) => Observable.of(this.loginError(error)))
));
Dado ese efecto secundario inicial, ¿cuál sería la forma "correcta" o "sugerida" de activar la navegación a una pantalla de "inicio" al iniciar sesión correctamente? Esto también podría generalizarse para simplemente desencadenar una secuencia de acciones u operaciones.
Algunas opciones que he considerado:
(a) ¿Otro efecto desencadenado por el éxito del inicio de sesión, que activa una acción posterior para activar la navegación?
@Effect navigateHome$: any = this.updates$
.whenAction(LoginActions.LOGIN_SUCCEEDED)
.mapTo(this.actions.navigateHome());
(b) ¿Otro efecto desencadenado por el éxito del inicio de sesión, que simplemente realiza la operación de navegación?
@Effect navigateHome$: any = this.updates$
.whenAction(LoginActions.LOGIN_SUCCEEDED)
.do(this.navigateHome())
.filter(() => false);
(c) Concatenar una acción adicional a las emitidas por el efecto de inicio de sesión inicial? (muestra obviamente no es del todo correcto, pero da la idea)
@Effect login$: any = this.updates$
.whenAction(LoginActions.LOGIN)
.map(toPayload)
.switchMap(password => Observable.concat(
this.loginService.login(passcode)
.map(result => this.actions.loginSuccess(value))
.catch((error) => Observable.of(this.loginError(error))),
Observable.of(this.actions.navigateHome())
));
(d) Other?
Hipótesis 2
Considerar un caso en el que una serie de solicitudes deben ser hecho en secuencia, y a medida que comienza cada solicitud queremos actualizar el "estado" para que se pueda proporcionar retroalimentación al usuario.
Ejemplo de un pensamiento para algo en esas líneas:
multiphaseAction() {
return (dispatch) => {
dispatch(this.actions.updateStatus('Executing phase 1');
this.request1()
.flatMap(result => {
dispatch(this.actions.updateStatus('Executing phase 2');
return this.request2();
})
.flatMap(result => {
dispatch(this.actions.updateStatus('Executing phase 3');
return this.request3();
})
...
}
}
Nuevamente, ¿cuál sería la forma "correcta" o "sugerida" de hacer esto al usar el enfoque de efectos?
En este estoy más atascado, no estoy realmente seguro de lo que se podría hacer aparte de agregar algunos .do(this.store.dispatch(this.actions.updateStatus(...))
de alguna manera...
4 answers
La respuesta para el escenario de navegación es su respuesta b
@Effect navigateHome$: any = this.updates$
.whenAction(LoginActions.LOGIN_SUCCEEDED)
.do(this.router.navigate('/home'))
.ignoreElements();
Explicación: Reaccionas al LOGIN_SUCCESS, y debido a que el router no devuelve una nueva acción, necesitamos detener la propagación del flujo, lo que hacemos filtrando todo.
Si se olvida de filtrar, el router devuelve indefinido, lo que a su vez llevará al reductor a reducir un valor indefinido, lo que generalmente resulta en un puntero nulo cuando intenta leer el type
del acción
Otra forma de solucionarlo es usar https://github.com/ngrx/router-store
Consulte la documentación sobre cómo agregar router-store a su aplicación.
El mismo efecto ahora se verá así.
import { go } from '@ngrx/router-store';
@Effect navigateHome$: any = this.updates$
.whenAction(LoginActions.LOGIN_SUCCEEDED)
.map(() => go(['/home']));
La acción go
enviará una acción de enrutador que el reductor de enrutador recogerá y activará un cambio de ruta.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-14 10:31:06
Hipótesis 1
Considere si navigateHome
debe o no cambiar el estado. También, si esta acción navigateHome
es enviada o no desde otros lugares para lograr lo mismo. Si es así, devolver una acción es el camino a seguir. Por lo tanto, opción A.
En algunos casos la opción B podría tener más sentido. Si navigateHome
solo cambia la ruta, podría valer la pena considerarlo.
Nota al margen: puedes usar ignoreElements aquí en lugar de filter(() => false)
.
Escenario 2
Sugeriría encadenar sus acciones aquí en múltiples efectos y dar retroalimentación modificando el estado en el reductor para cada acción.
Por ejemplo:
@Effect() trigger$: Observable<Action> = this.updates$
.whenAction(Actions.TRIGGER)
.do(() => console.log("start doing something here"))
.mapTo(Actions.PHASE1);
@Effect() phase1$: Observable<Action> = this.updates$
.whenAction(Actions.PHASE1)
.do(() => console.log("phase 1"))
.mapTo(Actions.PHASE2);
@Effect() phase2$: Observable<Action> = this.updates$
.whenAction(Actions.PHASE2)
.do(() => console.log("phase 2"))
.ignoreElements();
Y dar retroalimentación modificando el estado:
function reducer(state = initialState, action: Action): SomeState {
switch (action.type) {
case Actions.TRIGGER: {
return Object.assign({}, state, {
triggered: true
});
}
case Actions.PHASE1: {
return Object.assign({}, state, {
phase1: true
});
}
case Actions.PHASE2: {
return Object.assign({}, state, {
phase1: false,
phase2: true
});
}
// ...
}
}
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-28 09:06:48
Hipótesis 1
La opción A y B son buenas para ir, creo, pero tal vez la opción A es un poco demasiado solo para la navegación! También puede utilizar directamente el router en su LoginActions.LOGIN
si considera la navegación como parte de este efecto secundario.
Hipótesis 2
No hay nada de malo en encadenar @Effects
.
Envía una acción (SET_1_REQUEST) que desencadena tu efecto A. el efecto A devuelve una acción (SET_1_SUCCESS) que es recogida por tu reductor (ahora puedes mostrar ese estado al usuario) y se recoge por efecto B.
Espero que esto tenga sentido.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-21 06:21:46
Estoy aprendiendo estas cosas también.
¿Qué pasa si se envía al reductor cuando el primero en la cadena se hizo?
Algo como esto:
const myReducer = (state,action:Action) => {
switch action.type {
case : "my_first_step" : runfirststep(action.payload);
case : "my_second_step": runsecondstep(action.payload);
....
function runfirststep(data) {
...
//bunch of stuff to do,
//update something to display
store.dispatch('my_second_step');
}
Etc.
El ejemplo ngrx hace esto en el archivo de efectos. Cuando el add o update, llaman UPDATE_COMPLETE o algo similar presumiblemente para que se pueda hacer alguna notificación.
Poco a poco estoy captando lo grande que los reductores terminarán siendo en una aplicación moderadamente compleja.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-06-19 02:04:20