There are two models in a one-to-many relationship:
export interface GroupModel {
id: number;
name: string;
userIds?: number[];
}
export interface UserModel {
id: number;
name: string;
groupId?: number;
}
An issue arises when updating either model with an effect, the changes do not update the corresponding association:
groups.effects.ts
updateGroup$ = createEffect(() =>
this.actions$.pipe(
ofType(GroupsActions.updateGroup),
concatMap(({ group }) =>
this.groupsService.update(group).pipe(
map(() =>
GroupsAPIActions.groupUpdatedSuccess({
update: { id: group.id, changes: group },
})
),
catchError((error) =>
of(GroupsAPIActions.groupUpdatedFail({ message: error }))
)
)
)
)
);
users.effects.ts
updateUser$ = createEffect(() =>
this.actions$.pipe(
ofType(UsersActions.updateUser),
concatMap(({ user }) =>
this.usersService.update(user).pipe(
map(() =>
UsersAPIActions.userUpdatedSuccess({
update: { id: user.id, changes: user },
})
),
catchError((error) =>
of(UsersAPIActions.userUpdatedFail({ message: error }))
)
)
)
)
);
The reducer update methods are as follows:
on(
GroupsActions.groupStation,
(state) =>
({
...state,
loading: true,
errorMessage: '',
})
),
on(GroupsAPIActions.groupUpdatedSuccess, (state, { update }) =>
groupsAdapter.updateOne(update, {
...state,
loading: false,
})
),
on(
GroupsAPIActions.groupUpdatedFail,
(state, { message }) =>
({
...state,
loading: false,
errorMessage: message,
})
)
To summarize, let's consider the following models:
groups: [
{ id: 1, name: "Group 1", userIds?: [1, 2] }
];
users: [
{ id: 1, name: "User 1", groupId: 1 }
{ id: 2, name: "User 2", groupId: 1 }
];
If we call the dispatch method to remove User 2 from Group 1:
this.store.dispatch(UserActions.updateUser({
user: { id: 2, name: "User 2", groupId: undefined }
});
We want Group 1 to remove the userID from the userIds array as well:
{ id: 1, name: "Group 1", userIds?: [1] }
What would be a suitable approach to achieve this? Is there a standard solution or must custom coding be used?
Edit:
I have manually written the following code for the groupsReducer, but it seems like too much code for a simple task.
on(UsersAPIActions.userUpdatedSuccess, (state, { update }) => {
const userId = +update.id;
const newGroupId = update.changes.groupId;
const allGroups = selectAll(state);
// Find the group that contains the userId
const groupToUpdate = allGroups.find((group: GroupModel) =>
group.userIds.includes(userId)
);
const updateGroupChanges = {} as Partial<GroupModel>;
if (groupToUpdate) {
if (newGroupId) {
if (groupToUpdate.id === newGroupId) return state;
else {
const oldGroupChanges = {
userIds: groupToUpdate.userIds.filter((id) => id !== newGroupId),
} as Partial<GroupModel>;
const newGroupChanges = {
userIds: [...groupToUpdate.userIds, newGroupId],
} as Partial<GroupModel>;
console.log(`Group ${groupToUpdate.id}: Moving user ${userId} to group ${newGroupId}`);
return groupsAdapter.updateMany(
[
{ id: groupToUpdate.id, changes: oldGroupChanges },
{ id: newGroupId, changes: newGroupChanges },
],
{ ...state }
);
}
}
else {
updateGroupChanges.userIds = groupToUpdate.userIds.filter(
(id) => id !== newGroupId
);
}
}
else {
if (!newGroupId) return state;
const newGroup = selectGroupById(newGroupId)(state);
if (!newGroup) {
console.warn(`Group ${newGroupId}: Not found! Could not add user ${userId}`);
return state;
}
console.log(`Group ${newGroupId}: Adding user ${userId}`);
updateGroupChanges.userIds = [...newGroup.userIds, userId];
}
return groupsAdapter.updateOne(
{
id: newGroupId,
changes: updateGroupChanges,
} as Update<GroupModel>,
{ ...state }
);
}),