At first part you already mention that spliting reducer to small reducer so I would not mention again.
Secondly you can use NGRX/Entity to let NGRX handle CRUD operation for you so the code complexity would be reduce because NGRX/Entity already include some API allow developer to do CRUD operation on the state.
Example:
const userReducer = createReducer( initialState, on(UserActions.addUser, (state, { user }) => { return adapter.addOne(user, state) }), on(UserActions.upsertUser, (state, { user }) => { return adapter.upsertOne(user, state); }), on(UserActions.addUsers, (state, { users }) => { return adapter.addMany(users, state); }), on(UserActions.upsertUsers, (state, { users }) => { return adapter.upsertUsers(users, state); }), on(UserActions.updateUser, (state, { user }) => { return adapter.updateOne(user, state); }), on(UserActions.updateUsers, (state, { users }) => { return adapter.updateMany(users, state); }), on(UserActions.mapUsers, (state, { entityMap }) => { return adapter.map(entityMap, state); }), on(UserActions.deleteUser, (state, { id }) => { return adapter.removeOne(id, state); }), on(UserActions.deleteUsers, (state, { ids }) => { return adapter.removeMany(ids, state); }), on(UserActions.deleteUsersByPredicate, (state, { predicate }) => { return adapter.removeMany(predicate, state); }), on(UserActions.loadUsers, (state, { users }) => { return adapter.addAll(users, state); }), on(UserActions.clearUsers, state => { return adapter.removeAll({ ...state, selectedUserId: null }); }));