Using Ngrx Effects Lifecycle hooks OnInitEffects (seems to emit infinitely) - ngrx

I'm trying to learn a bit about effect lifecycle hooks introduced in Ngrx 7 and I'm not really understanding something that's happening. I've got an Angular app and I have the following in an effects class, however, the init$ observable is emitting values infinitely. I would have expected it to fire once and complete. I am somewhat new to observables as well. The documentation doesn't really help me out as there aren't really many examples. I could add a take(1), but I would like to understand why it continues to emit forever.
#Injectable()
export class AuthEffects implements OnInitEffects{
constructor(private actions$: Actions) {}
#Effect({dispatch: false})
login$ = this.actions$.pipe(
ofType<LoginAction>(AuthActionTypes.LoginAction),
tap(console.log)
);
#Effect({dispatch: false})
logout$ = this.actions$.pipe(
ofType<LogoutAction>(AuthActionTypes.LogoutAction),
tap(console.log)
);
#Effect()
init$ = this.actions$.pipe(
ofType<Action>('[Auth] Effects Init'),
tap(console.log)
);
ngrxOnInitEffects(): Action {
console.log('AuthEffects init\'d');
return { type: '[Auth] Effects Init'};
}
}

This is the expected behaviour - effects' primary usage is to act on some third party side effects. And you set ofType<Action> so it emits whenever any Action happens, while for ex:
#Effect({dispatch: false})
login$ = this.actions$.pipe(
ofType<LoginAction>(AuthActionTypes.LoginAction),
tap(console.log)
);
emits whenever LoginAction happens.

Related

How to have a Subject as a payload in an immutable Ngrx action?

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).

is it possible to keep redux dispatch function in class performing async actions?

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.

NGRX bulk effect of already defined single effect

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.

Understanding Ngrx OnRunEffects

I have several Effect listeners that need to run until the user has logged out. As such, I'm trying to implement OnRunEffects.
A segment of my user.actions.ts is:
export const LOAD_USER = '[Users] Load User';
export class LoadUser implements Action {
readonly type = LOAD_USER;
}
export type UserActions = LoadUser;
I'm trying to implement OnRunEffects as:
ngrxOnRunEffects(resolvedEffects: Observable<EffectNotification>) {
return this.actions
.ofType(UsersActions.LOAD_USER)
.exhaustMap(() => resolvedEffects.takeUntil(AuthActions.LOGOUT_SUCCESS)
}
However, takeUntil is complaining that it needs as Observable. So I tried:
ngrxOnRunEffects(resolvedEffects: Observable<EffectNotification>) {
return this.actions
.ofType(UsersActions.LOAD_USER)
.exhaustMap(() => resolvedEffects.takeUntil(
Observable.of(new AuthActions.LogoutSuccess())
)
}
That in turn, causes the effect to not fire. From reading the code, it looks like it states:
When you see these actions
run the effects
until you see this other action.
What am I doing wrong?
Correct me if I'm wrong, but you might not want to run until the user has logged out, you might just want to run those effects if the user is still logged in. If yes, then instead of thinking to takeUntil:
#Effect()
someEffect$: Observable<Action> = this.actions$
.ofType<Your_Type>(Your_Action)
.withLatestFrom(this.store$.select(state => state.users.isUserLoggedIn))
.filter(([action, isUserLoggedIn]) => isUserLoggedIn)
// here, you'll receive the actions only is the user is logged in
// ... your code for the effect
Try this:
ngrxOnRunEffects(resolvedEffects: Observable<EffectNotification>) {
return this.actions
.ofType(UsersActions.LOAD_USER)
.exhaustMap(() => resolvedEffects.takeUntil(this.actions.ofType(AuthActions.LOGOUT_SUCCESS)))
}
Documentation have been updated :
https://github.com/ngrx/platform/commit/4b606d581d18866576b9632b0537953c2b6006c4

Issue with clearing state from an ngrx/redux store when the user logs out

My application uses ngrx/rxjs. I rely on an ngrx effect in order to signout and clear state from the store.
Unfortunately, because one of my components subscribes to the store through a selector (see below: getLatestMessagesByCounterParty) and because the state is cleared before this component is destroyed, I get the following error:
ERROR TypeError: Cannot read property 'id' of null
at getCurrentUserAccountId
... indicating that the currentUserAccount is null, which is quite logical since I have just cleared the state from the store.
Here is the signout$ effect:
#Effect()
signout$: Observable<Action> = this.actions$
.ofType(authenticated.ActionTypes.SIGNOUT)
.switchMap(() =>
this.sessionSignoutService.signout()
.do(() => {
localStorage.removeItem('authenticated');
localStorage.removeItem('sessionToken');
})
.concatMap(() => [
new ClearMessagesAction(null),
new ClearUserAccountAction(null),//Error thrown here...
go(['/signin'])//Never reached...
]));
Here is the component that subscribes to the logged-in state:
ngOnInit() {
this.store.select(fromRoot.getLatestMessagesByCounterParty)
.subscribe(latestMessages => this.latestMessages = this.messageService.sortMessagesByDate(latestMessages, this.numberOfConversations));
}
And the relevant selectors:
...
const getCurrentUserAccountId = (state: State) => state.userAccount.currentUserAccount.id;
const getMessagesState = (state: State) => state.message.messages;
...
export const getLatestMessagesByCounterParty = createSelector(getCurrentUserAccountId, getMessagesState, fromMessage.latestMessagesByCounterParty);
I am looking for best practices on where, when and how to clear state from the store. Ideally I would like to do that at the last possible time, when the subscribing components have been destroyed.
Can someone please advise?
edit: Let me further refine my comment. My code above should have read as follows.
.concatMap(() => [
new ClearMessagesAction(null),
new ClearUserAccountAction(null),//Error thrown right after this action because selector cannot find id variable on state
go(['/signin'])//Never reached...
]));
As #cgatian said, you might use a filter. But here's what would happen behind the scene with that code :
.concatMap(() => [
new ClearMessagesAction(null),
new ClearUserAccountAction(null),//Error thrown here...
go(['/signin'])//Never reached...
]));
You'd first dispatch an action ClearMessagesAction(null).
Then that action will be handled by your reducer.
___A new state will be produced
___Your selectors will be triggered right after
___An error will occur because you end up with an inconsistent store (as you expect that the other action ClearUserAccountAction(null) was dispatched as the same time and before the selectors kick in)
What you should do to avoid state inconsistency, is either :
- Create one action that you handle in both reducers. This way, your reducers will both be modified and only then, selectors will kick in
- Use a library that allows you to dispatch multiples actions as one (like redux-batched-actions). This way you could write something like that :
batchActions([
new ClearMessagesAction(null), --> selectors not triggered yet
new ClearUserAccountAction(null) --> selectors not triggered yet
]); --> selectors triggered now

Resources