问题
I want to achieve setting a global state while also requesting data from an api and storing that in the state as well, but in another location than the global state.
I'm calling this effect (models.effects.ts
):
@Effect() models$: Observable<Action> = this.actions$
.ofType(GET_MODELS)
.switchMap(() => this.modelsApi.getModels())
.map(models => ({type: SET_MODELS, payload: models}))
.catch((err: any) => Observable.of({type: GET_FAILURE, payload: {error: err}}))
Now what I want to do is something like this:
@Effect() models$: Observable<Action> = this.actions$
.ofType(GET_MODELS)
.do(() => this.store.dispatch({type: 'SET_LOADING_STATE', payload: true}))
.switchMap(() => this.modelsApi.getModels())
.map(models => ({type: SET_MODELS, payload: models}))
.do(() => this.store.dispatch({type: 'SET_LOADING_STATE', payload: false}))
.catch((err: any) => Observable.of({type: GET_FAILURE, payload: {error: err}}))
As you can see we're dispatching a call to the globalReducer
(global.reducer.ts
):
export const GlobalReducer: ActionReducer<any> = (state: IGlobalStorage = {isLoading: false}, action: Action) => {
switch(action.type) {
case SET_LOADING_STATE: return Object.assign({}, state, {
isLoading: action.payload
});
default: return state;
}
}
Which would mean that I update the global state isLoading
before and after we make an http request. However, this solution is both messy and does not work because of the simple fact that it breaks the effect (for whatever reason, I think it's because I call dispatch within the effect).
Preferably I would like to create another effect which listens to SET_LOADING_STATE
which then calls the globalReducer
itself, rather than letting the models$
effect do it directly.
Something like this (from within global.effects.ts
):
@Effect() loadingState$: Observable<Action> = this.actions$
.ofType(SET_LOADING_STATE)
.do(() => ({type: SET_LOADING_STATE, payload: thePayloadThatWasSent}))
But there are 2 problems with that:
- I don't know how to access a sent payload in an effect.
- I don't know how to call that effect from within the
models$
effect.
Overall I'm just really confused on how to achieve what I want and there aren't any examples of this as far as I've been able to find.
If you look at this image, I want to update global.isLoading
when I update models
:
What would be the best way to achieve what I want?
回答1:
Storing an isLoading
indicator in a central place is a similar to what is sometimes done with error information. One solution for storing a central error involves using a reducer that ignores actions' types and looks only to see if they contain an error
property.
If you were to adopt a suitable naming scheme for your effects' action types, you could do much the same thing with isLoading
.
One possible naming scheme could be:
SOME_ACTION_REQUEST
SOME_ACTION_RESPONSE
SOME_ACTION_ERROR
With such a scheme, the following reducer would examine the action types and set the isLoading
state accordingly:
export function isLoadingReducer(state: boolean = false, action: Action): boolean {
if (/_REQUEST$/.test(action.type)) {
return true;
} else if (/(_RESPONSE|_ERROR)$/.test(action.type)) {
return false;
} else {
return state;
}
}
Using a boolean
value for isLoading
assumes that you would not have concurrent asynchronous effects, so if that's an issue, you could extend the reducer to use a counter instead.
If you wire things up this way, the isLoading
indicator doesn't need to know anything about individual effects and vice versa.
来源:https://stackoverflow.com/questions/40487872/angular-2-update-global-state-as-a-side-effect-of-updating-a-certain-state