I am currently using ngrx in my app to manage state. I was looking at switching to ngrx-data for the simple CRUD operations and ran across my first question. This is my current regular ngrx effect and I want to know how to reproduce it with ngrx-data (create new action based on http response):
#Effect()
insertUser$: Observable<Action> = this.action$.pipe(
ofType(UsersActionTypes.UserInsert),
map((action: UserInsert) => action.payload),
mergeMap(payload =>
this.UserService.insert(<IUserPersistRequest>{
user: payload.User,
refreshToken: payload.loggedInUser.RefreshToken
}).pipe(
map((response: IUserPersistResponse) => {
return !response.accessToken
? new UserPersistSuccessLoginFailure()
: new UserInsertSuccess({
response: response,
User: payload.User
});
}),
catchError((error: string) => {
return of(new UserInsertFailure('UserInsert Failed'));
})
)
)
);
Any advice?
Related
I'm using #ngrx/router-store and setting up a CustomerSerializer to manage routes in my state tree. I'm using the example in the #ngrx/router-store configuration docs. Also setup a RouterEffects class with a navigate$ effect to a specified path:
navigate$ = createEffect(():any => {
return this.actions$.pipe(
ofType(RouterActions.GO),
map((action: RouterActions.Go) => action.payload),
tap(({ path, query: queryParams, extras }) => {
this.router.navigate(path, { queryParams, ...extras });
})
)
}
);
When I dispatch a "GO" action with a payload, the Angular router navigates to the specified path, but I get this error in the console:
ERROR Error: Effect "RouterEffects.navigate$" dispatched an invalid action: {"path":["/admin"]}
I don't understand where the secondary action is being dispatched.
The problem is map((action: RouterActions.Go) => action.payload),
An effect always dispatches the next value back to the store.
This should be an action, but in this case it's the action's payload, resulting in the error.
To fix this you need to create non-dispatching effect.
navigate$ = createEffect(():any => {
return this.actions$.pipe(
ofType(RouterActions.GO),
map((action: RouterActions.Go) => action.payload),
tap(({ path, query: queryParams, extras }) => {
this.router.navigate(path, { queryParams, ...extras });
})
)
}
// add this 👇
, {dispatch: false}
);
Am new to Ngrx, We got stuck with the implementation , Need some help
Architecture:
We are trying to fill the store when the User is authenticated by that time am trying to redirect.
Expectation:
Effects should be called as async and In ui we have to do the redirect
Problem:
Redirection to Homepage is happening only after the ngrx/effect api call is done.
this.userService.authenticateUser(this.userId.toUpperCase(), this.password, (user) => {
this.authenticatedUser = user;
//Call the ngrx dispatchMethod to fill the store
this.router.navigateByUrl("/home");
this.ngrxGetModule_data();
async ngrxGetModule_data() {
this.store.dispatch({ type: Affaire_Action.Affaire_Addstore_Login });
//this.store.dispatch({ type: Affaire_Action.Initial_Store });
}
#Effect()
updateAffaireStore$: Observable<Action> = this.actions$
.ofType(Affaire_Action.Affaire_Addstore_Login)
.map<Action, string>(toPayload)
.switchMap(res => this.affaireService.getAffairesSearchSuggetions('')
//.delay(10000)
.map(res => (
{
type: Affaire_Action.Affaire_on_Success, payload: ({ "parameter": SearchSuggestion, "data": res })
}
)))
.catch(error => Observable.of({ type: Affaire_Action.Affaire_on_Failure, payload: null }))
What are you actually trying with the this.userService.authenticateUser ?
Seems like you are trying to call the function but the way you are doing is wrong.
What is the return type?!!! Observable or promise.
Suggestion: You should call services on you effect and dispatch actions from effect. You can also use this.router.navigateByUrl("/home"); on your effect.
I am trying to modify an effect I have made into letting me start and stop multiple firestore queries by using two actions. Currently the effect allows me to start and stop a single firestore query by listening for two separate actions in the effect. I simply use a switchMap to switch into an empty observable when there is a stop action. This works just fine.
#Effect()
startStopQuery$ = this.actions$.pipe(
ofType(
ActionTypes.START,
ActionTypes.STOP
),
switchMap(action => {
if (action.type === ActionTypes.STOP) {
return of([]);
} else {
return this.afs.collection('collection', ref => {
return ref.where('field', '==', 'x');
}).stateChanges();
}
}),
mergeMap(actions => actions),
map(action => {
return {
type: `[Collection] ${action.type}`,
payload: { id: action.payload.doc.id, ...action.payload.doc.data() }
};
})
);
What I actually want to do is to have multiple queries ongoing that I can start and stop with those same two actions, but where it depends on the action payload. When I modified it everytime I performed a new query the last one stops working. I think it is because the switchMap operator switches away from my last query observable. This is the best I have come up with:
#Effect()
startStopQueryById$ = this.actions$.pipe(
ofType(
ActionTypes.START_BY_ID,
ActionTypes.STOP_BY_ID
),
switchMap(action => {
if (action.type === ActionTypes.STOP_BY_ID) {
return of([]);
} else {
return this.afs.collection('collection', ref => {
return ref.where('field', '==', action.id);
}).stateChanges();
}
}),
mergeMap(actions => actions),
map(action => {
return {
type: `[Collection] ${action.type}`,
payload: { id: action.payload.doc.id, ...action.payload.doc.data() }
};
})
);
As I said, I think the issue is the switchMap operator. But that is also what I depended on to make the "stop" work in the first place. I cant seem to come up with another solution as I am not very well versed in the style yet.
Any help would be greatly appreciated!
I came up with a solution. I make an object that maps ID's to the firestore statechanges observables. On the start action I make the listener and adds it to the object. I make sure that it automatically unsubscribe by piping takeUntil with the corresponding stop action. It returns a merge of all the observables in the object and I silply do as before. I also have a seperate effect triggered by the stop action to remove the observable from the object. It looks like so:
queriesById: {[id: string]: Observable<DocumentChangeAction<Element>[]>} = {};
#Effect()
startQuery$ = this.actions$.pipe(
ofType(ActionTypes.START_BY_ID),
switchMap(action => {
this.queriesByPlay[action.pid] = this.afs.collection<Element>('requests', ref => {
return ref.where('field', '==', action.id);
}).stateChanges().pipe(
takeUntil(
this.actions$.pipe(
ofType(ActionTypes.STOP_BY_ID),
filter(cancelAction => action.id === cancelAction.id),
)
)
);
return merge(
Object.values(this.queriesByPlay)
);
}),
mergeMap(actions => actions),
mergeMap(actions => actions),
map(action => {
return {
type: `[Collection] ${action.type}`,
payload: { id: action.payload.doc.id, ...action.payload.doc.data() }
};
})
);
Effect({dispatch: false})
stopQuery$ = this.actions$.pipe(
ofType(ActionTypes.STOP_BY_ID),
map(action => delete this.queriesByPlay[action.id]),
);
This seems to work and have no problems except for being convoluted hard to understand.
I seek help from you for the first time as I am in deep trouble.
I am using ngrx effects to load some shop items into the cart on app init from firebase realtime database. As the payload I get the whole firebase database snapshot object instead of just the shop items themselves, therefore the ngrx store receives an object it cannot understand and the app state does not change.
Check the photos please: when console.logged(), I see the exact objects that I need. But the Redux Devtools reveal the real deal, and I do not know how to fix this. Could somebody give me advice? Thank you very, very much.
The effect looks like this:
#Effect()
getShopItems: Observable<any> = this.actions.pipe(
ofType(shopActions.GET_ITEMS),
switchMap(() => {
return of(this.database.ref('cart').once('value', snap => {
snap.forEach(childSnap => childSnap.val());
// snap.forEach(childSnap =>
// console.log(childSnap.val()));
}))
.pipe(
map((payload) => {
return {
type: 'GET_ITEMS_SUCCESS',
payload: payload
};
}
));
})
);
The reducer functions for the actions in question look like:
case shopActions.GET_ITEMS: {
return {
...state,
shopItems: [...state.shopItems],
itemCount: state.shopItems.length
};
}
case shopActions.GET_ITEMS_SUCCESS: {
return {
...state,
shopItems: [action.payload],
itemCount: state.shopItems.length + 1
};
}
https://imgur.com/a/pDGAwFI
Use from rather than of:
import { from } from 'rxjs';
#Effect()
getShopItems: Observable<any> = this.actions.pipe(
ofType(shopActions.GET_ITEMS),
switchMap(() => {
return from(this.database.ref('cart').once('value', snap => {
snap.forEach(childSnap => childSnap.val());
}))
.pipe(
map((payload) => {
return {
type: 'GET_ITEMS_SUCCESS',
payload: payload
};
}
));
})
);
RxJs from - https://www.learnrxjs.io/operators/creation/from.html
Usually I use redux-saga, but currently I need redux-thunk. I'm using ducks for modular structure and now for example I have two ducks: auth and user with async actions below:
auth-duck.js
register(credentials) {
return dispatch => {
dispatch(actions.registerRequest());
return service.requestRegister(credentials)
.then((response) => {
dispatch(actions.registerSuccess(...));
// Here I need to dispatch some action from user-duck.js
})
.catch(() => dispatch(actions.registerError(...)))
}
}
user-duck.js
fetchUser() {
return dispatch => {...}
}
I really don't know how to not mess these two modules and dispatch fetchUser after successful register.
I could return register result (e.g. token or something else) to container from here it was dispatched and then using chaining dispatch fetchUser.
AuthContainer.js
_onSubmit() {
this.props.register().then(() => this.props.fetchUser);
}
But I don't know is it the best way to manage such operations with redux-thunk?
There is no rule thunks can only make one HTTP request. If you need to fetch the user after login, then fetch it.
const login = credentials => dispatch => {
fetchLogin(credentials).then(() => {
dispatch({ type: "LoginSuccess" })
return fetchUser()
}).then(() => {
dispatch({ type: "UserFetched" })
})
}
If you want to reuse the user fetch action, then dispatch a thunk from a thunk.
const fetchCurrentUser = login => dispatch => {
return fetchUser(login.userId).then(user => {
dispatch({ type: "UserLoad" })
return user
})
}
const login = credentials => dispatch => {
return fetchLogin(credentials).then(login => {
dispatch({ type: "LoginSuccess" })
return dispatch(fetchCurrentUser(login))
}
}
In one of my apps, I call 7 action thunks after successful login.
After a long search I found two options how to share the logic from separate domains.
The first one is to use mapDispatchToProps (Thanks #DonovanM), just like this:
function mapDispatchToProps(dispatch) {
return {
login: (credentials) => {
return dispatch(authActions.login(credentials)).then(
() => dispatch(userActions.fetchUser())
);
}
}
}
login function returns Promise thats why we can chain it to another one.
And the second possible option:
Use something like a "bridge" file in our case it is app-sagas.js
app-duck.js
import {actions as authActions} from './auth-duck.js';
import {actions as userActions} from './user-duck.js';
export function doLogin(credentials) {
return dispatch => {
return dispatch(authAction.login(credentials)).then(
() => dispatch(userActions.fetchUser())
);
}
}
In the second case we can place all logic into ducks and avoid spreading the logic within containers. But I guess it is possible to combine both methods.