I have equivalent data types stored on 2 different databases.
Both types of data are stored in my ngrx
store ApplicationState
export interface ApplicationState {
serverFoodData: FoodData;
userFoodData: FoodData;
mergedFoodData: FoodData;
}
I need to keep a version of each of these so that I can save additional userFoodData
overrides if a user adds them, and so that I can recompute the mergedFoodData
when that happens.
As such, I need to detect when both http
requests have been resolved to the store and trigger the action to compute a new mergedFoodData
object. How can I do this?
Currently I am dispatch()
ing and action to load each from app.component.ts
constructor(private store: Store<ApplicationState>) {
this.store.dispatch(new LoadFoodsAction());
this.store.dispatch(new LoadUserDataAction(1)); // 1 = temporary userID
// this.foodStates$ = store.select(foodStatesSelector);
// this.foodStates$.subscribe(foodStates => this.currentFoodStates = foodStates);
// this.store.dispatch(new BuildMergedFoodDataAction(this.currentFoodStates))
}
As you can see I have tried to dispatch an action to merge the 2 FoodData
objects, but it is dispatch()
ing with unfulfilled values because the http
call has not yet responded.
actions.ts
// SQL FOODS
export const FOODS_LOADED_ACTION = 'FOODS_LOADED_ACTION';
export const LOAD_FOODS_ACTION = 'LOAD_FOODS_ACTION';
export class FoodsLoadedAction implements Action {
readonly type = FOODS_LOADED_ACTION;
constructor(public payload?: AllFoodData) { } // '?' is important
}
export class LoadFoodsAction implements Action {
readonly type = LOAD_FOODS_ACTION;
constructor(public payload: number) { }
}
// USER FOODS ACTIONS
export const LOAD_USER_DATA_ACTION = 'LOAD_USER_DATA_ACTION';
export const USER_DATA_LOADED_ACTION = 'USER_DATA_LOADED_ACTION';
export class UserDataLoadedAction implements Action {
readonly type = USER_DATA_LOADED_ACTION;
constructor(public payload?: AllUserData) { }
}
export class LoadUserDataAction implements Action {
readonly type = LOAD_USER_DATA_ACTION;
constructor(public payload: number) { }
}
// MERGE FOODS ACTION
export const BUILD_MERGED_FOOD_DATA_ACTION = 'BUILD_MERGED_FOOD_DATA_ACTION';
export class BuildMergedFoodDataAction implements Action {
readonly type = BUILD_MERGED_FOOD_DATA_ACTION;
constructor(public payload: FoodStates) { }
}
LoadFoodsEffectService.ts listens for the dispatch
from app.component.ts
and calls the http
service
@Injectable()
export class LoadFoodsEffectService {
@Effect() foods$: Observable<Action> = this.actions$
.ofType(LOAD_FOODS_ACTION)
.switchMap( () => this.foodService.loadServerFoods(1) ) // 1 = userID
.map(allFoodData => new FoodsLoadedAction(allFoodData) );
constructor(private actions$: Actions, private foodService: FoodService) {
}
}
food-service.ts called from the effects service
@Injectable()
export class FoodService {
constructor(private http: Http) { }
loadServerFoods(userId: number): Observable<AllFoodData> {
return this.http.get('/api/foods', commonHttpHeaders(userId)) // see proxy.config.json for the url
.map( res => res.json() );
}
}
serverFoodDataReducer.ts the http
response is then added to the Application State
export function serverFoodData(state: ServerFoodState = INITIAL_FOOD_DATA, action: Action): ServerFoodState {
switch (action.type) {
case FOODS_LOADED_ACTION:
return handleFoodsLoadedAction(state, action);
default:
return state;
}
}
export function handleFoodsLoadedAction(state: ServerFoodState, action: FoodsLoadedAction): ServerFoodState {
return { foods: _.keyBy(action.payload.foods, 'id') };
}
Again, how can I trigger a new action, once both of the http
calls have been reduced into the store?
In this case, you will need an action that triggers both of the requests to the server, and then triggers the 3 actions. I think something like this should work for you:
@Effect()
food$ = this.actions$
.ofType('LoadFood') // This one is to trigger BOTH foods, you would have
// another action for just the SQL or Mongo data.
.switchMap(action => {
return Observable.combineLatest(
service.loadServerFood(),
service.loadUserFood()
)
}).switchMap(([serverFood, userFood]) => {
return Observable.of(
serverFoodLoadedAction(serverFood),
userFoodLoadedAction(userFood),
mergeFoodAction(serverFood, userFood)
);
})