Reputation: 400
Details
I've been working on a project where I'm getting data from an API. In order to fetch these data, I store what I need in the store. For example, in the store, I'm storing the currently selected period, the currently bbox of a map and the data the is being fetched from the API. What I need to achieve is to update the data in the store every time there is a change in the selected period or the current bounding box of the map.
For this, I've created an Action, a Reducer and an Effect for Periods, BBox and Stats. The logic that I'm following is below and I'm using it for BBox and Stats too.
Code
export interface IPeriodState {
periods: Period[];
isLoading: boolean;
error: Error;
}
In reducers, I just mutate the state and nothing more. My actions are in the same logic at this one:
import { Action } from "@ngrx/store";
import { Period } from "../../models/Period";
export enum PeriodActionTypes {
ADD_PERIOD = "[Period] Add Period",
ADD_PERIOD_SUCCESS = "[Period] Add Period Success",
ADD_PERIOD_FAILURE = "[Period] Add Period Failure",
REMOVE_PERIOD = "[Period] Remove Period",
REMOVE_PERIOD_SUCCESS = "[Period] Remove Period Success",
REMOVE_PERIOD_FAILURE = "[Period] Remove Period Failure",
UPDATE_PERIOD = "[Period] Update Period",
UPDATE_PERIOD_SUCCESS = "[Period] Update Period Success",
UPDATE_PERIOD_FAILURE = "[Period] Update Period Failure"
}
// Add Period
export class AddPeriodAction implements Action {
readonly type = PeriodActionTypes.ADD_PERIOD;
constructor(public payload: Period) {}
}
export class AddPeriodSuccessAction implements Action {
readonly type = PeriodActionTypes.ADD_PERIOD_SUCCESS;
constructor(public payload: Period) {}
}
export class AddPeriodFailureAction implements Action {
readonly type = PeriodActionTypes.ADD_PERIOD_FAILURE;
constructor(public error: Error) {}
}
// Remove Period
export class RemovePeriodAction implements Action {
readonly type = PeriodActionTypes.REMOVE_PERIOD;
constructor(public id: string) {}
}
export class RemovePeriodSuccessAction implements Action {
readonly type = PeriodActionTypes.REMOVE_PERIOD_SUCCESS;
constructor(public id: string) {}
}
export class RemovePeriodFailureAction implements Action {
readonly type = PeriodActionTypes.REMOVE_PERIOD_FAILURE;
constructor(public error: Error) {}
}
// Update Period
export class UpdatePeriodAction implements Action {
readonly type = PeriodActionTypes.UPDATE_PERIOD;
constructor(public id: string, public payload: Period) {}
}
export class UpdatePeriodSuccessAction implements Action {
readonly type = PeriodActionTypes.UPDATE_PERIOD_SUCCESS;
constructor(public id: string, public payload: Period) {}
}
export class UpdatePeriodFailureAction implements Action {
readonly type = PeriodActionTypes.UPDATE_PERIOD_FAILURE;
constructor(public error: Error) {}
}
export type PeriodActions =
| AddPeriodAction
| AddPeriodSuccessAction
| AddPeriodFailureAction
| RemovePeriodAction
| RemovePeriodSuccessAction
| RemovePeriodFailureAction
| UpdatePeriodAction
| UpdatePeriodSuccessAction
| UpdatePeriodFailureAction;
In effects now, I'm dispatching multiple actions. For example:
//imports omitted
@Injectable()
export class PeriodEffect {
constructor(private actions: Actions) {}
@Effect() addPeriod$ = this.actions.pipe(
ofType<PeriodActions>(PeriodActionTypes.ADD_PERIOD),
mergeMap((action: AddPeriodAction) => [
new AddPeriodSuccessAction(action.payload),
new LoadStatsAction()
]),
catchError(error => of(new AddPeriodFailureAction(error)))
);
@Effect() removePeriod$ = this.actions.pipe(
ofType<PeriodActions>(PeriodActionTypes.REMOVE_PERIOD),
mergeMap((action: RemovePeriodAction) => [
new RemovePeriodSuccessAction(action.id),
new LoadStatsAction()
]),
catchError(error => of(new RemovePeriodFailureAction(error)))
);
@Effect() updatePeriod$ = this.actions.pipe(
ofType<PeriodActions>(PeriodActionTypes.UPDATE_PERIOD),
mergeMap((action: UpdatePeriodAction) => [
new UpdatePeriodSuccessAction(action.id, action.payload),
new LoadStatsAction()
]),
catchError(error => of(new UpdatePeriodFailureAction(error)))
);
}
Problem
My problem is that I got the LoadStatsAction
dispatched multiple times because I need to get the new data in the store, every time that there is a change in the website. Let's take a look at my period-selector
component. Once there is a change in the period, I manage this change with the below code:
constructor(private moment: MomentPipe, private store: Store<AppState>) {
this.currentPeriod = new Period(
id(),
new Date(2017, 9, 1),
new Date(2018, 9, 1)
);
this.store.dispatch(new AddPeriodAction(this.currentPeriod));
}
ngOnInit() {}
ngOnDestroy() {
this.store.dispatch(new RemovePeriodAction(this.currentPeriod.id));
}
updateCurrentPeriod(period: IPeriod) {
this.currentPeriod._minDate = period.minDate;
this.currentPeriod._maxDate = period.maxDate;
this.store.dispatch(
new UpdatePeriodAction(this.currentPeriod.id, this.currentPeriod)
);
}
Summary
Is there a way to get rid of the multiple dispatched actions?
Note
You can find the full code of the project here and a demo of the project here
Upvotes: 1
Views: 948
Reputation: 15497
If I've the question right, I think the question is that you WANT to dispatch the actions more than once but you don't want to add it in the 3 effects? If that's the case you can do the following:
We can listen to multiple actions inside the ofType
operator, use switchMap
or exhaustMap
to cancel multiple requests when the current request has not fulfilled yet.
@Effect() addPeriod$ = this.actions.pipe(
ofType(PeriodActionTypes.ADD_PERIOD_SUCCESS, PeriodActionTypes.REMOVE_PERIOD_SUCCESS, UPDATE_PERIOD_SUCCESS),
switchMap((action) => {
// logic here to load stats
// or to dispatch the LoadStatsAction action
}),
);
Upvotes: 1
Reputation: 1138
I would recommend of extracting the logic of when to dispatch a LoadStatsAction
action to avoid repetitive code, it should look like this:
@Effect() addPeriod$ = this.actions.pipe(
ofType<PeriodActions>(PeriodActionTypes.ADD_PERIOD),
map((action: AddPeriodAction) => new AddPeriodSuccessAction(action.payload)),
catchError(error => of(new AddPeriodFailureAction(error)))
);
@Effect() removePeriod$ = this.actions.pipe(
ofType<PeriodActions>(PeriodActionTypes.REMOVE_PERIOD),
map((action: RemovePeriodAction) => new RemovePeriodSuccessAction(action.id)),
catchError(error => of(new RemovePeriodFailureAction(error)))
);
@Effect() updatePeriod$ = this.actions.pipe(
ofType<PeriodActions>(PeriodActionTypes.UPDATE_PERIOD),
map((action: UpdatePeriodAction) => new UpdatePeriodSuccessAction(action.id, action.payload)),
catchError(error => of(new UpdatePeriodFailureAction(error)))
);
@Effect() loadStats$ = this.actions.pipe(
ofType(
AddPeriodSuccessAction,
RemovePeriodSuccessAction,
UpdatePeriodSuccessAction
),
mapTo(new LoadStatsAction())
)
Now you can focus on reducing the times LoadStatsAction
is dispatched, use debounceTime(x)
or throttleTime(x)
(a distinction between these operators can be found here)
Upvotes: 3