I have the following NGRX effects defined in a single class. The loadPage$
effect works fine. The loadSection$
effect works, but for some reason is triggering both the SetPages action and the SetSections action even though I'm only returning the SetSections action. Any ideas?
Effects
@Effect()
loadPages$: Observable<Action> = this.actions$.pipe(
ofType<GetPages>(GET_PAGES),
switchMap(() => {
return this.loanService
.getPages()
.pipe(
pluck('data', 'pages'),
tap((pages: Page[]) => pages[0].active = true),
map(pages => new SetPages(pages))
);
})
);
@Effect()
loadSections$: Observable<Action> = this.actions$.pipe(
ofType<SelectPage>(SELECT_PAGE),
switchMap((action: SelectPage) => {
console.log('loadPageSection$::action:', action);
return this.loanService
.getPageSections(action.payload)
.pipe(
pluck('data', 'pageSections'),
map(sections => new SetSections(sections))
);
})
);
Actions: All the actions defined in a single file.
export const GET_PAGES = 'GET_PAGES';
export const SELECT_PAGE = 'SELECT_PAGE';
export const SET_PAGES = 'SET_PAGES';
export const SET_SECTIONS = 'SET_SECTIONS';
export class GetPages implements Action {
readonly type = GET_PAGES;
}
export class SelectPage implements Action {
readonly type = SELECT_PAGE;
constructor(public payload: string) {
}
}
export class SetPages implements Action {
readonly type = SET_PAGES;
constructor(public payload: Page[]) {
}
}
export class SetSections implements Action {
readonly type = SET_SECTIONS;
constructor(public payload: Section[]) {
}
}
export type LoanActions = GetPages | SelectPage | SetPages | SetSections;
Reducers: When I put a console.log under SET_PAGES and SET_SECTIONS, I see both instances logged during the loadSection$ effect.
export function loanReducer(state: LoanState = initialState, action: LoanActions) {
switch (action.type) {
case SELECT_PAGE:
const pages = [ ...state.pages ];
deactivatePage(pages);
activatePage(action.payload, pages);
return { ...state, pages };
case SET_PAGES:
return { ...state, pages: [ ...action.payload ] };
case SET_SECTIONS:
return { ...state, sections: [ ...action.payload ] };
case GET_PAGES:
default:
return state;
}
}
EDIT adding active/deactivate functions
export const deactivatePage = (pages: Page[]) => chain(pages).find(page => page.active).set('active', false).value();
export const activatePage = (id: string, pages: Page[]) => chain(pages).find(obj => obj.id === id).set('active', true).value();
The action is triggered from a button click inside a custom component:
this.store.dispatch(new SelectPage(page.id))
I found the culprit. I'm using a GraphQL backend and the Apollo client in my Angular app to query the server. I was using the Apollo client's watchQuery
method to fetch the data.
According to the watchQuery
documentation
As you know, Apollo.query method returns an Observable that emits a result, just once. Apollo.watchQuery also does the same, except it can emit multiple results. (The GraphQL query itself is still only sent once, but the watchQuery observable can also update if, for example, another query causes the object to be updated within Apollo Client's global cache.)
When toggling the active flag on the data, it must trigger the watchQuery
's cache to be udpated thus triggering the other Ngrx effect. Converting my watchQuery
to a plain query
solved the issue.