Is it possible to both dispatch an array of actions and also navigate from an ngrx effect? - ngrx

I have an issue with one of my application's ngrx effects. I am basically trying to execute multiple actions using concatMap() AND navigate using the router store's go().
Here is the effect:
#Effect()
loadPersonalInfoAndSignin$: Observable<Action> = this.actions$
.ofType(session.ActionTypes.LOAD_PERSONAL_INFO)
.map((action: LoadPersonalInfoAction) => action.payload)
.do(sessionToken => {
localStorage.setItem('authenticated', 'true');
localStorage.setItem('sessionToken', sessionToken);
})
.switchMap(() => this.userAccountService
.retrieveCurrentUserAccount()
.concatMap(currentUserAccount => [
new LoadUserAccountAction(currentUserAccount),
new SigninAction(),
new LoadMessagesAction({})
])
)
.mapTo(go(['/dashboard']));
If I remove the .mapTo(go(['/dashboard'])), then all three actions in the concatMap array are successfully dispatched to their corresponding effects.
I am therefore wondering why my mapTo(go(... is causing the last two actions in the array (i.e. SigninAction & LoadMessagesAction) not to be dispatched to their corresponding effects..
Can someone please help?
edit: Changing mapTo to do as follows:
.do(go(['/dashboard']));
results in the following error:
ERROR in /Users/julien/Documents/projects/bignibou/bignibou-client/src/app/core/store/session/session.effects.ts (55,9): Argument of type 'Action' is not assignable to parameter of type 'PartialObserver<SigninAction>'.
Type 'Action' is not assignable to type 'CompletionObserver<SigninAction>'.
Property 'complete' is missing in type 'Action'.

Using do for the go call will not see the route changed. go is an action creator and the action that it creates needs to be emitted from the effect for #ngrx/router-store to receive the action and effect the route change.
Also, the mapTo operator will ignore what it receives and will emit the value you've specified, so it's not appropriate, either.
Instead, you should include the action created by the go call in your concatMap array:
#Effect()
loadPersonalInfoAndSignin$: Observable<Action> = this.actions$
.ofType(session.ActionTypes.LOAD_PERSONAL_INFO)
.map((action: LoadPersonalInfoAction) => action.payload)
.do(sessionToken => {
localStorage.setItem('authenticated', 'true');
localStorage.setItem('sessionToken', sessionToken);
})
.switchMap(() => this.userAccountService
.retrieveCurrentUserAccount()
.concatMap(currentUserAccount => [
new LoadUserAccountAction(currentUserAccount),
new SigninAction(),
new LoadMessagesAction({}),
go(['/dashboard'])
])
);

Related

ngrx effects combine action resposne with multiple selectors values

I have an Action defined MyAction.loadDataSuccess.
When ever loadDataSuccess is success ,we need to dispatch other three action.that am trying to implemented here. Other Actions needed loadDataSuccess response along with selectData1 and selectData2
Here is my code.
loadSuccess$ = createEffect(() => this.actions$.pipe(
ofType(MyActions.loadDataSuccess),
concatMap((action: any) => of(action).pipe(
withLatestFrom(this.store$.select(selectData1)),
withLatestFrom(this.store$.select(selectData2))
)),
switchMap(([payload, data1,data2]: any) => [
FileActions.setSelectedName({ fileName: payload.name[0] }),
PFDataActions.loadPFData({ data1: data1, data2:data2 }),
OtherDataActions.loadOtherData(
{ data1: data1, data2:data2, otherName: ''}
),
MainDataActions.loadMainData(
{ data1: data1, data2:data2, otherName: '' }
),
])
));
The above code was working when we have only one withLatestFrom.
Now the above code throwing FileActions.setSelectedName undefined.
How can we combine action response with two selector values
i have combined both selectData1 and selectData2 into a single selector,but that won't worked.
Some one have any solutions, feel free to update
Note: ngrx version-13
withLatestFrom can take multiple source observables: https://rxjs.dev/api/operators/withLatestFrom
ofType(MyActions.loadDataSuccess),
withLatestFrom(this.store$.select(selectData1), this.store$.select(selectData2)),
switchMap(([payload, [data1,data2]]: any) =>

ngrx effect - waiting on external data

I have an effect which runs when a user is downloaded and stored in state:
getMetadata$ = createEffect(() => {
return this.actions$.pipe(
ofType(fromUserSelectors.getUserSuccess),
concatLatestFrom(() => this.store.select(fromProductSelectors.selectProduct)),
exhaustMap(([user, product]) =>
// do something with user and product
)
);
});
This effect is triggered when getUserSuccess is dispatched.
However, you can see on the 4th line, I also need the product which is stored in a different store and is downloaded asynchronously to the user being downloaded. So it isn't necessarily populated until later.
How can I tell this effect to wait for the product to be populated before resuming? I do not want to retrigger the effect. I thought that was the purpose of concatLatestFrom introduced in ngrx v12, but maybe I am misunderstanding it.
I am aware I could have multiple actions being listened to in my ofType call and listen to fromProductSelectors.getProductSuccess, but I only want this effect to run when getUserSuccess is called, not fromProductSelectors.getProductSuccess too, but need the selectProduct to not be undefined.
You can use the filter operator to wait until it becomes available.
getMetadata$ = createEffect(() => {
return this.actions$.pipe(
ofType(fromUserSelectors.getUserSuccess),
concatLatestFrom(() => this.store.select(fromProductSelectors.selectProduct).pipe(filter(x => !!x)),
exhaustMap(([user, product]) =>
// do something with user and product
)
);
});

ngrx - effect for group of actions

i have a store with few actions: play, stop, clear, change
all of the actions have effects and they work great.
my question is, is there a way to create a single effect for this group? if one of this actions is dispatched, i want a separate effect that sends a new action(btnsStateChaned)
i don't want to add my new action in every effect
i want something to replace all the || operators(they don't work as well):
updateBtnsState$: Observable<Action> = createEffect(() =>
this.actions$.pipe(
ofType(gridStateActions.Play || gridStateActions.Stop || gridStateActions.ClearActive || gridStateActions.ChangeVideo),
map(_ => new gridStateActions.UpdateButtonsState())
)
);
UPDATE
updateBtnsState$: Observable<Action> = createEffect(() =>
this.actions$.pipe(
ofType(gridStateActions.GRID_ACTIONS.PLAY, gridStateActions.GRID_ACTIONS.STOP, gridStateActions.GRID_ACTIONS.CLEAR_ACTIVE, gridStateActions.GRID_ACTIONS.CHANGE_VIDEO),
map(_ => new gridStateActions.UpdateButtonsState())
)
);
this is working, still, i would like to know if there is a better way
that's exactly the way as you specified in your question:
ofType(
gridStateActions.GRID_ACTIONS.PLAY,
gridStateActions.GRID_ACTIONS.STOP,
gridStateActions.GRID_ACTIONS.CLEAR_ACTIVE,
gridStateActions.GRID_ACTIONS.CHANGE_VIDEO,
),
and there's no other official way to do it.

Dispatch action in effect before return statement

I need to dispatch/trigger another ui-related action inside an effect before calling the service to fetch data without injecting the store
I managed to fix it by injecting the store in the constructor and dispatch this extra action in effect right before calling the service for fetching the data(this.store.dispatch(UIActions.startLoading()) but I am not sure if injecting the store in the effect is a good practice
recipes$ = createEffect(() => this.actions$.pipe(
ofType(RecipeActions.FETCH_RECIPES),
switchMap(() => this.recipeService.getRecipes().pipe(
switchMap(recipes => [
RecipeActions.setRecipes({ recipes }),
UIActions.stopLoading()
]),
catchError(() => EMPTY)
))
));
I wonder if there is a way to do this like using
tap(() => of(UIActions.startLoading())) inside the first switchMap
You're correct in the use of tap operator for your desired functionality.
However, you need to do store.dispatch instead of simply returning an of observable.
Also, instead of multiple switchMap, you can use the from observable to return array of actions.
recipes$ = createEffect(() => this.actions$.pipe(
ofType(RecipeActions.FETCH_RECIPES),
tap(()=> this.store.dispatch(UIActions.startLoading())),
switchMap(() => this.recipeService.getRecipes().pipe(
map(recipes => from([
RecipeActions.setRecipes({ recipes }),
UIActions.stopLoading()
]),
catchError(() => EMPTY)
))
));

How to emit one event after finishing previous one using NgRx?

I am new to this technology and I am working on a project where blow's the scenario.
table schema:
tbl.Product
id
product name
brand_id [FK]
tbl.Brand
id
BrandName
in this, while submitting I am adding BrandName first and taking the newly added id of brand record and insert full record in product table with brand_id.
Submit(){
this.saveBrand.emit(brand);
product.brand_id = brands.id;
this.saveProduct.emit(product);
}
for this, I am using NgRx Store.
Brand.effects.ts
addBrand$: Observable<Action> = this.actions$
.pipe(ofType<Add>(BrandActionTypes.Add),
switchMap(action => this.BrandService.addBrand(action.payload)),
map((Brand: any) => new AddSuccess(Brand)),
catchError(err => {
toastr.error(`Could not add Brand.`);
console.error(err);
return of(new AddFail(err));
})
);
Product.effects.ts
addProduct$: Observable<Action> = this.actions$
.pipe(ofType<Add>(ProductActionTypes.Add),
switchMap(action => this.ProductService.addProduct(action.payload)),
map((Product: any) => new AddSuccess(Product)),
catchError(err => {
toastr.error(`Could not add Product.`);
console.error(err);
return of(new AddFail(err));
})
);
while executing this both event emitting together (both are not async). so product is not getting brand_id.
i am expecting output like.
Submit(){
this.saveBrand.emit(brand);
product.brand_id = brands.id;
this.saveProduct.emit(product); \\hold until brand get added
}
Dispatching an action is an async process, that's why the brand id isn't "populated".
To solve your problem you can do a few things:
your products effects can listen on the AddSuccess action
use one effect to handle both service calls
make on service call?
A great article about some of the patterns NgRx: Patterns and Techniques

Resources