Ngrx Entities - Simplify Your Ngrx State
In this post you will learn what is NgRx entity and how you can reduce the amount of boilerplate when you are using NgRx.
So this post is for you if you are already familiar with NgRx and you understand concepts like reducers, selectors, effects and now you are curious what is ngrx-entity
and how you can use it to simplify your code.
Here is the library which is called ngrx/entity
and this is an entity state adapter for managing record collections. This line for me doesn't bring a lot of clarity. But for you to understand it correctly, this is not some new stuff inside NgRx, these are just some helper methods that allow us to work with collections more efficiently. It won't change your patterns of actions, effects and reducers. It just simplifies them a bit.
Installation
So our first step here is to install this package.
npm install @ngrx/entity
Additionally I already prepared for us a project with standard stuff like ngrx/store
and ngrx/effects
.
This is how our initial project looks like. Here we have a list of posts that we fetch from the API. We can also create new posts. They will stay there even after page reload because we make an API call to save them on the backend.
Also inside Redux Devtools you can see our actions to get and create posts.
State improvement
By now our project is build with plain NgRx. We have a reducer, 2 effects for getting a list and creating a post and actions to make these changes.
Now we want to apply to our code ngrx-entity
library. Our first step is to update our PostsStateInterface
.
// before
export interface PostsStateInterface {
posts: PostInterface[];
isLoading: boolean;
error: string | null;
}
// after
export interface PostsStateInterface extends EntityState<PostInterface> {
isLoading: boolean;
error: string | null;
}
We removed storing of our posts
completely as now EntityState
will store them and it uses such format.
{
"ids": ["1", "2"],
"entities": {"1": {"name": "User 1"}, "2": {"name": "User 2"}}
}
So it stores a list of ids and an object with entities.
Now inside our reducer we want to create an adapter and use methods to simplify our code.
// before
export const initialState: PostsStateInterface = {
posts: [],
isLoading: false,
error: null,
};
export const reducers = createReducer(
initialState,
on(PostsActions.getPosts, (state) => ({ ...state, isLoading: true })),
on(PostsActions.getPostsSuccess, (state, action) => {
return {...state, isLoading: false, posts: action.posts}
}),
on(PostsActions.getPostsFailure, (state, action) => ({
...state,
isLoading: false,
error: action.error,
})),
on(PostsActions.createPostSuccess, (state, action) => {
return {...state, isLoading: false, posts: [...state.posts, action.post]}
})
);
// after
export const adapter: EntityAdapter<PostInterface> = createEntityAdapter<PostInterface>();
export const initialState: PostsStateInterface = adapter.getInitialState({
isLoading: false,
error: null,
});
export const reducers = createReducer(
initialState,
on(PostsActions.getPosts, (state) => ({ ...state, isLoading: true })),
on(PostsActions.getPostsSuccess, (state, action) => {
return adapter.addMany(action.posts, { ...state, isLoading: false });
}),
on(PostsActions.getPostsFailure, (state, action) => ({
...state,
isLoading: false,
error: action.error,
})),
on(PostsActions.createPostSuccess, (state, action) => {
return adapter.addOne(action.post, { ...state, isLoading: false });
})
);
Here we did several things. First of all we created an adapter
by using createEntityAdapter
function. Then we created initialState
by using adapter.getInitialState
. After this we updated getPostsSuccess
and createPostSuccess
to add new elements to the list of posts. For this we used adapter.addMany
and adapter.addOne
methods.
The last thing what we need to change is our selector to get a list of posts.
// before
export const postsSelector = createSelector(
selectFeature,
state => state.posts
)
// after
export const postsSelector = createSelector(
selectFeature,
adapter.getSelectors().selectAll
)
Here we used adapter.getSelectors
which allows us to select either all elements as an array or a list of ids if we need it.
As you can see after all our changes the projects works the same but we used ngrx-entity
here.
Want to conquer your next JavaScript interview? Download my FREE PDF - Pass Your JS Interview with Confidence and start preparing for success today!
📚 Source code of what we've done