ngrx twice reducer call during effect - ngrx

I am developing using an angular app using ngrx. I have defined the convention below to implement a loading indicator:
Initially state of each entity is set to null
Make it an empty object on effect starts
Fill it with fetched data on effect done
Now one of my effects will be this:
#Effect()
LoginUser$ = this._actions$.pipe(
ofType<LoginUser>(EUserActions.LoginUser),
switchMap((params) => { new LoginUserSuccess(<IUser>{}); return of(params); }), // for loading indicator to be shown
switchMap((params) => this._userService.loginUser(params.payload)),
switchMap((currentUser: IUser) => of(new LoginUserSuccess(currentUser)))
)
but the reducer call in the first switchMap does not get occur. What is the problem.

An effect is a stream, only the last Action in the stream will be dispatched.
For your case you can listen on LoginUser in your reducer and empty your state.

I have finally solved my problem some other way. I now am dispatching another action, inside the primary action to update the state. For example this is how I have done it:
user.service.ts
export class UserService {
constructor(private _store: Store<IAppState>) { }
loginUser(model): void {
this._store.dispatch(new AddBusy(EUserActions.LoginUser));
this._store.dispatch(new LoginUser(model));
}
getAllUsers(): void {
this._store.dispatch(new AddBusy(EUserActions.GetAllUsers));
this._store.dispatch(new GetAllUsers());
}
}
user.actions.ts
export class UserEffects {
#Effect()
LoginUser$ = this._actions$.pipe(
ofType<LoginUser>(EUserActions.LoginUser),
switchMap((params) => this._userLogic.loginUser(params.payload)),
switchMap((currentUser: IUser) => { this._store.dispatch(new RemoveBusy(EUserActions.LoginUser)); return of(currentUser); }),
switchMap((currentUser: IUser) => of(new LoginUserSuccess(currentUser)))
)
#Effect()
getAllUsers$ = this._actions$.pipe(
ofType<GetAllUsers>(EUserActions.GetAllUsers),
switchMap(() => this._userLogic.getAllUsers()),
switchMap((users: IUser[]) => { this._store.dispatch(new RemoveBusy(EUserActions.GetAllUsers)); return of(users); }),
switchMap((users: IUser[]) => of(new GetAllUsersSuccess(users)))
)
constructor(
private _userLogic: UserLogic,
private _actions$: Actions,
private _store: Store<IAppState>,
) { }
}
This solved my problem nice.

Related

how to combine 2 selectors in ngrx effect

here's the scenario. i have this 2 different selectors which compose of different properties. what i want to happen is to combine them together which will be later on to be used as a parameters:
here's the codes:
#Injectable()
export class AuthenticationEffect {
getJwtToken$ = createEffect(() => {
return this.actions$.pipe(
ofType(TransactionIDActionTypes.LOAD_SUCCESS),
concatMap(action => of(action).pipe(withLatestFrom(
this.store.select(getTransactionIDSelector),
this.store.select(getAuthPayload)
))),
mergeMap(([, transactionId, authPayload]) => {
return this.authenticationService.getJwtToken(this.AuthenticateRequest(transactionId)).pipe(
map(response => {
return LoadSessionSuccess(response);
}),
catchError(error => of(LoadSessionError(error)))
);
})
);
});
constructor(private actions$: Actions, private store: Store<any>, private authenticationService: AuthenticateService) {}
AuthenticateRequest(transactionId): AuthenticateInterface {
return {
transactionId,
sourceIdentifier,
residentType,
organizationCode,
profile,
kycId,
identificationNumber,
mobileIdentifier,
nationality,
dateAcceptedTermsOfUse,
};
}
}
i need to execute this two selectors
this.store.select(getTransactionIDSelector),
this.store.select(getAuthPayload)
at the same time, is this possible? and how do i get the state value for each selectors?
You can use concatLatestFrom for this (instead of concatMap in combination with withLatestFrom).
...
concatLatestFrom(() => [
this.store.select(getTransactionIDSelector),
this.store.select(getAuthPayload)
]),
...

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$;
}),
);
});

Redux Observable ( Epic ) - concat two promises before returning action

I'm stuck trying to accomplish the following. In my React app, I am using redux-observable Epics.
I have two promises, one which needs to wait for the second, before it fires.
import { map, mergeMap, catchError } from 'rxjs/operators';
import { of } from 'rxjs/observable/of';
import { fromPromise } from 'rxjs/observable/fromPromise';
.....
const promise1 = Auth.getCredentials().then( credentials => {
return credentials
}
const promise2 = ( credentials ) => {
return doQuery(credentials, someData).then(function(data) {
// return success
}).catch(function(err) {
// reject error
});
}
So promise 2 needs the credentials from promise1, I am having a hard time knowing how to use observable/fromPromise etc to 'chaing' these items together so that the result of ends up either in the 'map' or 'catchError' result
In my Epic, i have something like this:
const searchEpic: Epic<RootAction, RootState> =
(action$, store) => action$.ofType(DO_QUERY)
.mergeMap(({payload}) => {
???????? - this is where Im stuck
const $stream = RESULT_OF_PROMISES.pipe(
map((response) => {
return actionCreators.success(..)
},
catchError(e => {
return of(actionCreators.failure(..));
}
)));
return $stream
});
Thank you!

Get list as array from Firebase with angularfire2(v5) and ngrx effects

I have to tell you I'm getting crazy with it. I'm trying to get data from Firebase with AngularFire2(v.5) then work with it on #ngrx/effects and store it on #ngrx/store. Well, as I need the data with the keys, my code of effects looks like this:
spaces.effects.ts
#Effect()
getSpaces$ = this.actions$.ofType(SpacesActions.GET_SPACES_REQUEST)
.switchMap((action: SpacesActions.GetSpacesRequest) => {
return this.afs.list<Space>('/spaces').snapshotChanges()
.switchMap(actions => {
console.log('action is ', actions);
return actions.map(space => {
const $key = space.payload.key;
const data: Space = { $key, ...space.payload.val() };
console.log('snapshot is: ', data);
return new SpacesActions.GetSpacesSuccess(data);
});
}
);
My "actions" comes with the data and the key, then I get the key for each item because then I could update and delete items easily. My database has 3 items with 3 keys. If I run this code and log it, first I can see all items in 1 array with their payloads and with the second log I see each payload as snapshot.
When I call GetSpacesSuccess, I'd like to send all snapshots I got (with key and item) then store it. The way I'm doing now dispatch this action 3 times and I can see only 2 items on the screen because the first one is overridden by the second one.
So, two questions: Is there any easier way to get the items from firebase with their keys then store them with #ngrx? If not, what am I doing wrong that my first item is being overridden and my action is being dispatched 3 times?
Please, I'm doing my best with it as I'm learning. Thank you!
spaces.reducers.ts
case SpacesActions.GET_SPACES_REQUEST:
return {
state,
spaces: null,
loading: true
};
case SpacesActions.GET_SPACES_SUCCESS:
return {
...state,
...action.payload,
spaces: [state, action.payload],
loading: false
};
spaces.actions.ts
export class GetSpacesRequest implements Action {
readonly type = GET_SPACES_REQUEST;
}
export class GetSpacesSuccess implements Action {
readonly type = GET_SPACES_SUCCESS;
constructor(public payload: Space) {} <<<<<HERE I'D LIKE TO GET THE FULL ARRAY WITH EACH KEY
}
Thanks #AndreaM16 for the most complete answer. I went through the night working on it and I ended up doing it different. Actually, in the learning process we make mistakes in order to get the knowledge. Probably your solution is better than mine and I'll study that, thanks. Please, if possible, I'd love to hear your comments about my solution.
Finally, after reading lots of documentation, my effects is now this one, I don't have any error catcher though:
private spacesList = 'spaces/';
#Effect()
getSpaces$ = this.actions$.ofType(SpacesActions.GET_SPACES_REQUEST)
.switchMap(payload => this.afs.list(this.spacesList).snapshotChanges()
.map(spaces => {
return spaces.map(
res => {
const $key = res.payload.key;
const space: Space = {$key, ...res.payload.val()};
return space;
}
);
})
.map(res =>
new SpacesActions.GetSpacesSuccess(res)
));
Reducer
case SpacesActions.GET_SPACES_REQUEST:
return Object.assign({}, state, {
spaces: null,
loading: true
});
case SpacesActions.GET_SPACES_SUCCESS:
return Object.assign({}, state, {
spaces: action.payload,
loading: false
});
Actions
export class GetSpacesRequest implements Action {
readonly type = GET_SPACES_REQUEST;
}
export class GetSpacesSuccess implements Action {
readonly type = GET_SPACES_SUCCESS;
constructor(public payload: Space[]) {}
}
And, in my component, where I need the list:
constructor(private store: Store<fromSpaces.FeatureState>) {}
ngOnInit() {
this.store.dispatch(new SpacesActions.GetSpacesRequest());
this.spacesState = this.store.select('spaces');
}
If I understood your question correctly, you would like to store for each Item also store its key. You are looking for Map.
I would structure your feature as follows.
spaces.actions.ts:
Loading spaces requires no payload, while success has only an array of Space. I think you should build your Map<string,Space> in your reducer (string is your key).
import { Action } from '#ngrx/store';
/** App Models **/
import { Space } from './space.model';
export const GET_SPACES = '[Spaces] Spaces get';
export const GET_SPACES_SUCCESS = '[Start] Spaces get - Success';
export class GetSpacesAction implements Action {
readonly type = GET_SPACES;
}
export class GetSpacesActionSuccess implements Action {
readonly type = GET_SPACES_SUCCESS;
constructor(public payload: Space[]) {}
}
export type All
= GetSpacesAction
| GetSpacesActionSuccess;
spaces.effects.ts:
I'm assuming you just need a method to fetch spaces. If you need to do other stuff, just edit this piece of code. spaceService.getSpaces() is supposed to return only an array of Spaces. So, create a new Space model and, on your service, map each json entry to a new Space().
import { Injectable } from '#angular/core';
import { Actions, Effect } from '#ngrx/effects';
/** rxjs **/
import {map} from 'rxjs/operators/map';
import {mergeMap} from 'rxjs/operators/mergeMap';
import {catchError} from 'rxjs/operators/catchError';
/** ngrx **/
import * as spacesActions from './spaces.actions';
/** App Services **/
import { SpacesService } from './spaces.service';
#Injectable()
export class SpacesEffects {
#Effect() getSpaces$ = this.actions$
.ofType(spaceActions.GET_SPACES)
.pipe(
mergeMap(() => {
return this.spaceService.getSpaces()
.pipe(
map((spaces) => {
return new spacesActions.GetSpacesActionSuccess(spaces);
}),
catchError((error: Error) => {
// Handle erro here
})
);
})
)
;
constructor(private spacesService: SpacesService, private actions$: Actions) { }
}
spaces.reducer.ts
Here you build your map and you can also create a new action to return, for instance, a space given its key. I dont think you need any loading parameter here, I guess you are using it for some loading handling in your views, just use AsyncPipe in your view and handle a loading animation with an *ngIf checking if there are spaces or not.
/** ngrx **/
import {createFeatureSelector} from '#ngrx/store';
import {createSelector} from '#ngrx/store';
import * as spacesActions from './spaces.actions';
export type Action = spacesActions.All;
/** App Models **/
import { Space } from './space.model';
export interface SpaceState {
keySpaces: Map<string, Space>;
spaces: Space[];
keys: string[];
}
export const initialState: SpaceState = {
keySpaces: new Map<string, Space>(),
spaces: [],
keys: []
};
// Selectors
export const selectSpace = createFeatureSelector<SpaceState>('space');
export const getKeySpaces = createSelector(selectSpace, (state: StartState) => {
return state.keySpaces;
});
export const getSpaces = createSelector(selectSpace, (state: SpaceState) => {
return state.spaces;
});
export const getKeys = createSelector(selectSpace, (state: SpaceState) => {
return state.keys;
});
export function spacesReducer(state: SpaceState = initialState, action: Action): SpaceState {
switch (action.type) {
case startActions.GET_SPACES_SUCCESS:
// Since we return this from effect
const fetchedSpaces = action.payload;
const fetchedKeys = [];
const keySpacesMap = new Map<string, Space>();
fetchedSpaces.forEach( (space: Space) => {
fetchedkeys = fetchedKeys.concat(space.key);
keySpacesMap.set(space.key, new Space(space));
}
returns {
...state,
keySpaces: keySpacesMap,
spaces: fetchedSpaces,
keys: fetchedkeys
}
default: {
return state;
}
}
}
And, finally, you should be able to get such parameters in your components like:
. . .
keySpaces$ = Observable<Map<string, Space>>;
spaces$ = Observable<Array<Space>>;
keys$ = Observable<Array<string>>;
constructor(private _store: Store<AppState>) {
this.keySpaces$ = this._store.select(getKeySpaces);
this.space$s = this._store.select(getSpaces);
this.keys$ = this._store.select(getKeys);
}
. . .
ngOnInit() {
this._store.dispatch(new spacesActions.GetSpacesAction);
}
. . .
Of course add the new state to AppState:
. . .
export interface AppState {
. . .
space: SpaceState;
}

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