So, Im working on an app with a concept of "Plans" and each plan you can add a comment. That part works fine, but it seems to fail and get confused if i try to run this in a loop.
The Action:
export class AddComment implements Action {
readonly type = CommentActionTypes.AddComment;
constructor(public payload: Comment) {}
}
export class AddCommentSuccess implements Action {
readonly type = CommentActionTypes.AddCommentSuccess;
constructor(public payload: Comment) {}
}
Effect
#Effect()
addComment$: Observable<Action> = this.actions$
.ofType<AddComment>(CommentActionTypes.AddComment).pipe(
switchMap(action => this.commentService.addComment(this.disciplineType, action.payload)),
map((comment: any) => new AddCommentSuccess(comment)),
catchError(err => of(new AddCommentFail(err)))
);
Implementation
What im struggling with is firing this off in rapid success/ I have a situation where I want to add a duplicate comment to multiple plans.
saveSet.forEach(x => {
comment.plan_id = x.id;
this.store.dispatch(this.buildAddCommentAction(comment));
});
For reference:
buildAddCommentAction(comment: DisciplineComment) : Action {
return new CommentActions.AddComment(comment);
}
What is Happening
If i have a list of 5 plans, and want to add a duplicate comment to all of them, Im only getting a successful response for the last item in the loop.
Now i know that is overly chatty, that is 5 separate client/service calls. What I cant figure out, its what the prescribed approach to this should be?
1.) A new BulkAddComment Action, effect, etc. Im loathe to do this becuase I have Comments, Concerns (similar in function and need), and one of each for every "discipline". Thatd be about 36 new effects and twice that in actions. A serious refactor is needed.
2.) Modify the actions and effects for 1 or multiple
3.)?
Thanks for input
This is because you're using the switchMap operator which will cancel the current running observable, in your case the service call.
You'll have to use concatMap or mergeMap. If the order is important use concatMap, if not use mergeMap because this will make your service calls in parallel.
For more info, watch this.
Related
Use case: dispatch an action with a cold observable in the payload.
When an effect catches the action, it subscribes (through mergeMap, switchMap, whatever...) to this observable and send back another action. Classic Ngrx process.
export class ServicesStore {
dispatchObservable(operation$: Observable<unknown>) {
this.store.dispatch(serviceRequestAction({ operation$ }));
}
}
export class ServicesEffects {
serviceRequest$ = createEffect(() =>
this.actions$.pipe(
ofType(serviceRequestAction),
mergeMap((action: ServiceRequestAction) => {
return action.operation$.pipe(
map((result) => {
// send back an action with the result
})
);
})
)
);
}
Usage:
this.servicesStore.dispatch(this.userService.getAll$());
It works well.
But if this observable is a Subject (say MatDialog.open().afterClosed()) it will break the immutable action Ngrx rule.
Because of the inner subscription, the Subject adds an observer into its internal structure, thus breaking the action immutability. It then triggers the Ngrx runtime checks.
Of course I can disable these check, but I am looking for a better away around this. For example, is there a way to clone a Subject ?
Or any other way to allow a Subject into the action payload ?
AFAIK adding a subject to a NgRx Action isn't supported (if you want to keep the runtime checks enabled).
The classic NgRx process is that the effect results in a new action (popular ones are success and failure).
I am beginner in Redux and I want to use it for asynchronous logic. Redux style quide recommends to use redux-thunk for it, but it seems I don't need it if I use redux in following way:
class Actions {
constructor(dispatch) {
this.dispatch = dispatch;
}
someSyncAction1(data) {
this.dispatch({
type: SOME_SYNC_ACTION1,
payload: data,
})
}
someSyncAction2(data) {
this.dispatch({
type: SOME_SYNC_ACTION2,
payload: data,
})
}
async someAsyncAction(data1, data2) {
this.someSyncAction1(data1);
await somethingAsync();
this.someSyncAction2(data2);
}
}
// then in my react component:
function MyComponent() {
const dispatch = useDispatch();
const actions = new Actions(dispatch);
//...
return <div onClick={() => actions.someAsyncAction(1, 2)}></div>;
}
It seems to be a simple way but I worry whether it can lead to errors. Please help me to understand what is wrong with it.
This is not very different from the useActions hook referred to in the Hooks documentation - at least for the synchronous stuff.
In the async stuff, you are losing functionality though: Thunks can at any given time access the current state by calling getState.
Also, and this is probably more important: thunks are not only recommended, they are a pattern almost every redux developer knows. So they look at your code and can immediately go to work. Your pattern on the other hand is not established, so it will lead to conflicts if someone other will ever take over your code - without any real benefit.
I am using redux-sage in my application and below is my code
export default function* () {
yield takeLatest(ActionTypes.VALIDATE_INPUT, checkForInputValidity);
yield takeLatest(ActionTypes.ON_REFRESH, onRefresh);
yield takeLatest(ActionTypes.ON_SUBMIT, onSubmit);
}
Is there a way to make sure I call a common generator function before any action is handled. For example,
whenever I dispatch some action, I want to update a variable in redux-state. this is common across all actions. What I am trying to avoid here is to duplicate some piece of common code in every action handler
You can use simple reducer function which skips action type check and track all the actions, and i.e. return the current timestamp:
export default () => + new Date()
You can call take with no arguments or with '*' in order to match all actions, per the docs.
The part that might get tricky (though still totally doable!) is getting the correct order of generator functions. Is it important the universal generator is run before the other generators, or is it ok if they are all run at once asynchronously? The docs on concurrency, fork vs spawn, and running tasks in parallel might illustrate the difference.
Untested, but I think this is what you want:
import {all, takeLatest, takeEvery } from "redux-saga/effects";
function* universal(action) {
// do something
// if dispatching anything here, make sure that the dispatched action doesn't get picked up again and create an infinite loop
}
export default function* () {
// univeral generator is called with a blocking yield
yield takeEvery( '*', universal);
// run individual generators in parallel to each other
yield all ([
takeLatest(ActionTypes.VALIDATE_INPUT, checkForInputValidity),
takeLatest(ActionTypes.ON_REFRESH, onRefresh),
takeLatest(ActionTypes.ON_SUBMIT, onSubmit),
]);
}
Alternatively, you could write some sort of custom middleware.
Is it possible to have two or more Sagas that handle the same message type?
For example two sagas that both implement IHandleMessages?
Does this work for all saga storages or only some of them?
Update:
I've tested I have two sagas (SendSMSSaga and SendEmailSaga) both Implements the same IHandleMessages (seperate IAmInitiatedBy commands) the only thing that happens in the first handle is:
if (!IsNew) return;
Data.Command = message;
Data.Id = message.SagaId ?? Guid.NewGuid();
Data.Status = Status.INIT;
Data.LogRecordId = Extensions.CreateLogRecordId();
await Bus.SendLocalWithHeader(new CreateLogCompleteCommand() { SagaId = Data.Id, LogRecordId = Data.LogRecordId });
//SendSMSSaga
protected override void CorrelateMessages(ICorrelationConfig config)
{
config.Correlate(x => x.SagaId, y => y.Id);
config.Correlate(x => x.SagaId, y => y.Id);
}
//SendEmailSaga
protected override void CorrelateMessages(ICorrelationConfig config)
{
config.Correlate(x => x.SagaId, y => y.Id);
config.Correlate(x => x.SagaId, y => y.Id);
}
Then I get the following exception:
5 unhandled exceptions: 13.07.2016 10:26:30 +02:00: System.ArgumentException: Object of type 'Unipluss.Sign.Notification.Queue.Saga.Email.SendEmailSagaData' cannot be converted to type 'Unipluss.Sign.Notification.Queue.Saga.SendSMSSagaData'.
Any tips on what I'm doing wrong?
I've tried both the SQL and the new AzureStorage saga implementations.
Yes it is possible, and it works for all saga storages(*).
You need to keep in mind though that each saga's data is updated separately, so if e.g. the last update experiences a ConcurrencyException, the message is rolled back and will be received again.
If this can cause issues for you, you should be sure to make your sagas idempotent.
(*) At the time when Rune asked the question it did NOT work as it should. There was a subtle bug in Rebus versions < 0.99.68 that would not include the saga data's type in the criteria when correlating by ID.
This would not be a problem in most cases, because it required multiple saga handlers to handle the same message in order to expose the bug.
It has been fixed in 0.99.68 for all the affected saga persisters.
I have this question in my head, not sure if this is validate or not, below it's an example of redux middle console log out the store.
const logger = store => next => action => {
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
return result
}
I can see it's using currying, so in redux is calling as logger(store)(store.dispatch)(action) (Correct me if i am wrong). My question is why we currying here instead just
(store, next, action) => { // do the rest }
Thanks for any suggestion I am slowly moving into functional programming too to get my head up rhythm with it.
I think redux wants to provide three hooks to developers.
We can split the call chain logger(store)(next)(action) into
let haveStoreAndDispatch = logger(store);
let haveNext = haveStoreAndDispatch(next);
let haveAction = haveNext(action);
Then we get three hook functions.
In haveStoreAndDispatch callback function, store have been created.
In haveNext callback function, we have get the next middleware.
In HaveAction callback function, we can do something with the previous middleware's result action.
Callbacks haveStoreAndDispatch and haveNext just be called only once in applyMiddleware(...middlewares)(createStore).