How to implement Generic reducer and action in NgRx - ngrx

I'm working on a Angular 9 app that uses the NGRX store and I'm new to NgRx. Could some one let me know how to develop a generic reducer/action where we don't need to write reducer and actions again and again. For an example I have userList and productLst which has similar kind of actions then how do we implement one generic list reducer/action and use it for both(userList and productLst)

Unfortunately, to distinguish states, entities and actions every feature should have own state, reducers and actions.
But you can use #ngrx/data to reduce boilerplate: https://ngrx.io/guide/data
There's EntityCollectionServiceFactory that will create reducers and actions for you.
constructor(EntityCollectionServiceFactory: EntityCollectionServiceFactory) {
this.heroService = EntityCollectionServiceFactory.create<Hero>('Hero');
this.filteredHeroes$ = this.heroService.filteredEntities$;
this.loading$ = this.heroService.loading$;
}
getHeroes() { this.heroService.getAll(); }
add(hero: Hero) { this.heroService.add(hero); }
deleteHero(hero: Hero) { this.heroService.delete(hero.id); }
update(hero: Hero) { this.heroService.update(hero); }
More info here: https://ngrx.io/guide/data/entity-collection-service#examples-from-the-demo-app

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.

How do I define a reducer at the top level?

I have two reducer files that handle the two areas of state in my module...
export interface MyState {
people: PeopleState;
pets: PetState;
}
export const reducers: ActionReducerMap<MyState> = {
people: peopleReducer,
pets: petsReducer
};
This works fine. However, I have some actions that need to update both areas of the state. I'd like to handle this by having a reducer file that deals with things at the MyState level and can update people and pets. I'd like to keep both the existing reducers that handle this at the lower levels.
I can't see how to register the top level reducer. The way the code is now, any reducer added to the ActionReducerMap must be added as a property inside MyState rather than handling MyState as a whole.
Anyone got any ideas?
Thanks
Nick
I guess that the only solution here is to use metaReducer, check this article: https://netbasal.com/implementing-a-meta-reducer-in-ngrx-store-4379d7e1020a
Description: metaReducer is a kind of reducer that stays above other reducers. It should have it's own actions, can have it's own effects. You can use it in such way:
export function metaReducer( reducer ) {
return function reduce( state: ParentState, action: MetaReducerAction ) {
switch(action.type)
case PARENT_ACTION:
return {
...state,
people: peopleReducer(state.people, new DoSthWithPeople()),
pets: petsReducer(state.pets, new DoSthWithPets())
}
default:
return reducer(state, action);
}
}
Where:
interface ParentState {
pets: PetsState,
people: PeopleState
}
type MetaReducerAction = ParentAction <-- this has type PARENT_ACTION
So the workflow is straight forward. In a place that you want a action to update both people and pets states, you need to dispatch PARENT_ACTION, then the actions DoSthWith... are going to be triggered on both slices of state. If you dispatch different action (the action of type that is not handled by the metaReducer, so sth different to PARENT_ACTION) then it will allow other reducers to handle the action (check whats in default section).
The last part is configuration, it should look like this:
StoreModule.forFeature(compose(metaReducer, combineReducers)(reducers))
Where reducers is just:
const reducers = {
pets: petsReducer,
people: peopleReducer
}
Edit: formatting

How should I deep-duplicate state data in Redux?

I have several instances of state where I need to support actions that duplicate some slice of state. For example, my product is a survey builder, so when I duplicate a question, I'd also like to duplicate its answers, rather than have multiple questions pointing to the same answer instances.
The state is normalized:
questionsById: {
q01: {
...
answers: ["a01"],
...
}
}
answersById: {
a01: {...}
}
When dispatching an action of QUESTION_DUPLICATE, I'd like to also duplicate any answers. Currently my QUESTION_DUPLICATE action creator also creates a mapped list of new answer keys, and then the answer reducer consumes this.
This pattern seems unwieldy to me, especially when considering the possibility of deeper duplications (for example, duplicating a Page, which contains Questions, which contain Answers...). Is there a better pattern for deeply duplicating normalized data?
The answer may revolve around how you normally handle normalizing and denormalizing your data. For example, in my blog post Practical Redux, Part 8: Form Draft Data Management, I reuse my existing normalization logic (which leverages the redux-orm library) to copy an item to be edited between the "current" and "draft" slices in my state. So, similarly, one approach would be to denormalize the question you want to duplicate, and then re-normalize it (in either the action creator or the reducer, as you see fit).
I settled on using normalizr & I came up with a recursive duplicator function. It accepts an entity, schema, and keygen function, & recursively updates any nested entities based on the schemata by giving them new ids. In the base case (when there are no further nested entities) it will return the basic thing with its key updated.
const duplicator = (entity, schema, keygen) => {
const newEntity = {
...entity,
[schema._idAttribute]: keygen(entity, schema)
};
if (Object.keys(schema.schema).length === 0) {
return newEntity;
}
return Object.keys(schema.schema).reduce(
(acc, nestedKey) => {
if (!entity.hasOwnProperty(nestedKey)) {
return acc;
}
if (!Array.isArray(schema.schema[nestedKey])) {
return {
...acc,
[nestedKey]: duplicator(
entity[nestedKey],
schema.schema[nestedKey],
keygen
)
};
}
return {
...acc,
[nestedKey]: acc[nestedKey].map((nestedEntity, index) =>
duplicator(nestedEntity, schema.schema[nestedKey][0], keygen)
)
};
},
{ ...newEntity }
);
};
export default duplicator;
This currently doesn't support the schema.Array setup of normalizr for multiple entity types in an array. I'm not currently using schema.Array and this case would be pretty non-trivial to support, but I'll consider it in the future.

"Thread safety" in Redux?

Let's pretend I have a long-running function working on computing my new state.
Meanwhile another action comes in and changes the state while the first one did not finish and is working on stuff.
If I am imagining things correctly there is no actions queue and the state might be resolved in some unpredictable manner.
Should I be worried about this at all?
I don't mean real threads, just a concept for the lack of better wording. Actions are asynchronous and state keys are being accessed by reference.
I was concerned about the same thing so I just did some digging. It looks like two threads concurrently calling dispatch() (if it were possible) could raise an exception. But it shouldn't be possible and that error message points to a particular, different cause. The "actions queue" is in the browser's own event loop. That event loop runs async/interaction callbacks (from which we call dispatch()) one-at-a-time.
That's the responsibility of your own action creators and your own reducers, and heavily related to how you structure your actions and reducers conceptually. The Redux FAQ question on structuring "business logic" is very relevant here:Redux FAQ
Thunk action creators have access to getState, so it's very common to have a thunk check the current state and only dispatch under certain conditions, such as this example:
// An example of conditional dispatching based on state
const MAX_TODOS = 5;
function addTodosIfAllowed(todoText) {
return (dispatch, getState) => {
const state = getState();
if(state.todos.length < MAX_TODOS) {
dispatch({type : "ADD_TODO", text : todoText});
}
}
}
Your reducer can also have sanity checks as well:
function todosReducer(state, action) {
switch(action.type) {
case "ADD_TODO": {
if(state.todos.length >= state.maxTodos) {
return state;
}
return {
...state,
todos : state.todos.concat(action.newTodo)
}
}
default : return state;
}
}
Personally, I don't like to have my reducers just blindly merge in whatever data's in the action, unless it's very small (like, say, the name of the currently selected tab or something). I prefer to have a reasonable amount of logic in my action creator to set up the action, a minimal-ish amount of data included in the action itself, and a sufficiently smart reducer to do the work based on that action.

Resources