How to get the store state in Effects using withLatestFrom operator - ngrx

I'm trying with this
payloadToBeSaved$ = createEffect(() => this.actions$
.ofType(SOME_ACTION)
.withLatestFrom(this.store$)
.map(([action: Action, storeState: AppState]) => {
// Do something ...
});
But this.actions$ is an observable and I have to use this.actions$.pipe()....
when I try with pipe() all lines red with errors. Don't know how to fix.

#Effect()
shipOrder = this.actions.pipe(
ofType<ShipOrder>(ActionTypes.ShipOrder),
map(action => action.payload),
concatMap(action =>
of(action).pipe(
withLatestFrom(store.pipe(select(getUserName)))
)
),
map([payload, username] => {
...
})
)
Reference: Start using ngrx/effects for this

Related

How do i can use concatLastestFrom?

I am try use effects with my store, but 2 days i have this error:
Idk why it happened, can u help me please?
stackblitz simulation
Action:
export const LoadMeetings = createAction('[ Meetings/Api ] Load Meetings');
Effect:
readonly loadMeetings$ = createEffect(() =>
this.actions$.pipe(
ofType(fromMeetingsActions.LoadMeetings),
concatLatestFrom(action => this.store.select(fromMeetings.selectMeetingsState)),
)
);
Selector:
export const selectMeetingsState =
createFeatureSelector<MeetingsStateModel>(meetingsFeatureKey);
//This doesn't work too
const selector = <T>(mapping: (state: MeetingsStateModel) => T) => createSelector(selectMeetingsState, mapping);
export const selectSelectedMeetingId = selector((state) => state.selectedMeetingId);
I try find answer in google, docs, youtube, q&a and any services, but zero feed back..
I try change operator map, concatMap, switchMap, pipe and another..
I try change selectors, but zero result
Effects usually map to an action ie:
readonly loadMeetings$ = createEffect(() =>
this.actions$.pipe(
ofType(fromMeetingsActions.LoadMeetings),
concatLatestFrom(action => this.store.select(fromMeetings.selectMeetingsState)),
map(() => fromMeetings.loadMeetingsSucceeded)
),
);
Stackblitz: https://stackblitz.com/edit/angular-pq8vlw?file=src%2Fmeetings%2F%2Bstate%2Feffects.ts
If they don't return an action you need to add { dispatch: false }:
readonly loadMeetings$ = createEffect(() =>
this.actions$.pipe(
ofType(fromMeetingsActions.LoadMeetings),
concatLatestFrom((action) =>
this.store.select(fromMeetings.selectMeetingsState)
)
),
{ dispatch: false }
);
Stackblitz: https://stackblitz.com/edit/angular-h2yifk?file=src%2Fmeetings%2F%2Bstate%2Feffects.ts

When handling in NgRX Effect, the effect won't work anymore

For the life of me, I can't figure out why once and error is thrown and intercepted, the effect will not work anymore
#Effect()
register$ = createEffect(() => {
return this.actions$.pipe(
ofType(RegisterAction),
map(action => action.registrationInfo),
mergeMap(registrationInfo => {
return this.authService.createUser(registrationInfo.email, registrationInfo.password,
registrationInfo.lastname, registrationInfo.firstname);
}),
map(credentialInfo => ProfileInitAction({credentialInfo})),
catchError(error => [ProfileInitErrorAction(error)]),
);
});
You know why? Because this is the normal workflow in RXJS. So an observable emits values at different times. When an error occurs, then the observable chain (or pipe or subscription, or what you want) breaks. You have a service call. This service call can fail, right? But you do this service call in a child observable chain. So you can handle the error in this child's observable chain, and this will not break your main observable chain. In one word, do the catchError in the mergeMap.
For example:
import { Injectable } from '#angular/core';
import { Actions, createEffect, ofType } from '#ngrx/effects';
import { EMPTY } from 'rxjs';
import { map, mergeMap, catchError } from 'rxjs/operators';
import { MoviesService } from './movies.service';
#Injectable()
export class MovieEffects {
loadMovies$ = createEffect(() => this.actions$.pipe(
ofType('[Movies Page] Load Movies'),
mergeMap(() => this.moviesService.getAll() //< --- starting point of the child observable chain
.pipe(
map(movies => ({ type: '[Movies API] Movies Loaded Success', payload: movies })),
catchError(() => EMPTY) // <--- this is runs in the child observable chain
))
) // <--- end of the child observable chain
); // <--- end of the main observable chain
constructor(
private actions$: Actions,
private moviesService: MoviesService
) {}
}
Adding caught$ in the catchError seems to fix this.
register$ = createEffect(() => {
return this.actions$.pipe(
ofType(RegisterAction),
map(action => action.registrationInfo),
mergeMap(registrationInfo => {
return this.authService.createUser(registrationInfo.email, registrationInfo.password,
registrationInfo.lastname, registrationInfo.firstname);
}),
map(credentialInfo => ProfileInitAction({credentialInfo})),
catchError((err, caught$) => {
notify(this.translate.instant('pages.auth.register.notify.error' + ': ' + err['message']), 'error');
return caught$;
}),
);
});

get type in withlatestFrom

I have the following selector
export const featureAdapter: EntityAdapter<IRoute> = createEntityAdapter<IRoute>({
selectId: model => model.routeId,
});
export interface State extends EntityState<IRoute> {
selectedRouteId: string;
selectedPointId: string;
}
export const selectAllEntities: (state: object) => Dictionary<IRoute> = featureAdapter.getSelectors(selectRouteState).selectEntities;
export const selectedR: MemoizedSelector<object, string> = createSelector(selectRouteState, getSelectedRoute);
export const selectedRoute: MemoizedSelector<object, IRoute> = createSelector(
selectAllEntities,
selectedR,
(entities, id) => entities[id]
);
that return something of type IRoute
In my effect I use a withlatest from
onAction$ = this.actions$.pipe(
ofType<featureActions.onAction>(featureActions.ActionTypes.onAction),
concatMap(action =>
of(action).pipe(
withLatestFrom(this.store$.pipe(select(RoutesStoreSelectors.getById(), {routeId: action.payload.routeId}))),
)
),
switchMap(([action, route]) => {})
)
inside the
switchMap(([action, route])
the route variable, is of type any. but it should be of type IRoute
how can I make it work correctly ?
I can't see why TypeScript cannot infer the type, but you can always explicitly specify the type.
onAction$ = this.actions$.pipe(
ofType<featureActions.onAction>(featureActions.ActionTypes.onAction),
concatMap(action =>
of(action).pipe(
withLatestFrom(this.store$.pipe(select(RoutesStoreSelectors.getById(), {routeId: action.payload.routeId}))),
)
),
switchMap(([action, route]: [Action, IRoute]) => {})
)
I am not sure why you need get hold of dispatched action in switchMap, but you can do the following to get the route of type IRoute -
onAction$ = this.actions$.pipe(
ofType<featureActions.onAction>(featureActions.ActionTypes.onAction),
switchMap(action =>
combineLatest(of(action), this.store$.pipe(select(RoutesStoreSelectors.getById(), {routeId: action.payload.routeId})))
),
switchMap(([action, route]) => {})
)
Having code like this will ensure to have the action of type of the dispatched action and route is of type IRoute.
I hope it helps.

"Error: Actions must be plain objects" on Array of actions returned from epics

Error: Actions must be plain objects. Use custom middleware for async actions.
On all set of actions returned from epics we get this error.
it could be due to how epics are set up in the epics creator:
const epic = (action$, store) =>
action$.ofType(String(key)).mergeMap(action => func(action, store))
either the mappings are not correct here or more probably they are not correct in the epics.
Example of one epic called by func(action, store):
[String(usersActions.updateUser)]: (action, store) => {
return authFetch(`${API_ROOT}/user/${get(action, 'payload.id')}`, {
method: 'put',
headers: {
...
},
body: ...,
})
.then(resJson => {
if (resJson['status'] === 200) {
return [
appActions.pushNotification('success', USER_UPDATE_SUCCESS),
appActions.setNextPath(`/users/${get(action, 'payload.id')}`),
]
} else
return [
appActions.pushNotification(
'error',
USER_UPDATE_FAILED + ': ' + resJson['message']
),
]
})
.catch(() => {
return [appActions.pushNotification('error', USER_UPDATE_FAILED)]
})
},
It works correctly if the square parenthesis are not there, tried with this suggestion too:
[String(usersActions.updateUser)]: (action, store) => {
return Rx.Observable.fromPromise(
fetch(`${API_ROOT}/user/${get(action, 'payload.id')}`, {
method: 'put',
headers: {},
}).then(res => res.json())
)
.mergeMap(resJson => {
if (resJson['status'] === 200) {
return Rx.Observable.concat(
Rx.Observable.of(
appActions.pushNotification('success', USER_UPDATE_SUCCESS)
),
Rx.Observable.of(
appActions.setNextPath(`/users/${get(action, 'payload.id')}`)
)
)
} else return
Rx.Observable.concat(
Rx.Observable.of(
appActions.pushNotification(
'error',
USER_UPDATE_FAILED + ': ' + resJson['message']
)
)
)
})
.catch(() => {
Rx.Observable.concat(
Rx.Observable.of(
appActions.pushNotification('error', USER_UPDATE_FAILED)
)
)
})
},
it removes the error but the actions dispatched are now duplicated.
Already tried will all the options out there so hopefully it's not a repeated question.
You can add a .do(action => console.log(action)) to the end of your epic to see what value(s) you're emitting and why they are not actions. e.g. if it's an array of actions, that's incorrect. Epics should only ever emit plain old javascript actions with a type property.

Redux observable cancel next operator execution?

I am using redux-observable with redux for async actions. Inside epic's map operator i am doing some pre processing because its the central place.
My app calling same action from multiple container components with different values.
So basically i have to cancel my ajax request/next operator execution if deepEqual(oldAtts, newAtts) is true
code -
export default function getProducts(action$, store) {
return action$.ofType(FETCH_PRODUCTS_REQUEST)
.debounceTime(500)
.map(function(action) {
let oldAtts = store.getState().catalog.filterAtts
let newAtts = Object.assign({}, oldAtts, action.atts)
if (deepEqual(oldAtts, newAtts)) {
// Don't do new ajax request
}
const searchString = queryString.stringify(newAtts, {
arrayFormat: 'bracket'
})
// Push new state
pushState(newAtts)
// Return new `action` object with new key `searchString` to call API
return Object.assign({}, action, {
searchString
})
})
.mergeMap(action =>
ajax.get(`/products?${action.searchString}`)
.map(response => doFetchProductsFulfilled(response))
.catch(error => Observable.of({
type: FETCH_PRODUCTS_FAILURE,
payload: error.xhr.response,
error: true
}))
.takeUntil(action$.ofType(FETCH_PRODUCTS_CANCEL))
);
}
Not sure whether its right way to do it from epic.
Thanks in advance.
You can do this:
export default function getProducts(action$, store) {
return action$.ofType(FETCH_PRODUCTS_REQUEST)
.debounceTime(500)
.map(action => ({
oldAtts: store.getState().catalog.filterAtts,
newAtts: Object.assign({}, oldAtts, action.atts)
}))
.filter(({ oldAtts, newAtts }) => !deepEqual(oldAtts, newAtts))
.do(({ newAtts }) => pushState(newAtts))
.map(({ newAtts }) => queryString.stringify(newAtts, {
arrayFormat: 'bracket'
}))
.mergeMap(searchString => ...);
}
But most likely you do not need to save the atts to state to do the comparison:
export default function getProducts(action$, store) {
return action$.ofType(FETCH_PRODUCTS_REQUEST)
.debounceTime(500)
.map(action => action.atts)
.distinctUntilChanged(deepEqual)
.map(atts => queryString.stringify(atts, { arrayFormat: 'bracket' }))
.mergeMap(searchString => ...);
}

Resources