NgRx is a popular state management library for Angular applications. It provides a centralized store for storing the application’s state and a set of tools for updating the state in a predictable manner. NgRx also provides a powerful set of reducers, which are functions that specify how the state should change in response to an action.
One of the core concepts in NGRX is immutability, which means that the state should never be modified directly. Instead, when an action is dispatched, the reducer creates a new copy of the state with the necessary changes. This helps ensure that the state remains predictable and that it can be easily tested and debugged.
However, creating a new copy of the state can be quite a pain, especially for complex state structures. To address this issue, ngrx-immer was created, which is a library that provides an easier way to handle immutability in NgRx reducers.
NgRx on
method vs immerOn
method
Consider a state with a list of users, where each user has a name and an age. We want to add a new user to the list, update the name of an existing user, and delete a user from the list.
Using the on
method, we would write the following NGRX reducer:
import { createReducer, on } from '@ngrx/store';
import { addUser, updateUser, deleteUser } from './user.actions';
export interface User {
name: string;
age: number;
}
export const initialState: User[] = [];
export const _userReducer = createReducer(
initialState,
on(addUser, (state, { user }) => [...state, user]),
on(updateUser, (state, { user, name }) =>
state.map(u => (u.name === user.name ? { ...u, name } : u))
),
on(deleteUser, (state, { user }) =>
state.filter(u => u.name !== user.name)
)
);
Using the immerOn
method from ngrx-immer
, we would write the following NgRx reducer:
import { createReducer, immerOn } from 'ngrx-immer';
import { addUser, updateUser, deleteUser } from './user.actions';
export interface User {
name: string;
age: number;
}
export const initialState: User[] = [];
export const _userReducer = createReducer(
initialState,
immerOn(addUser, (state, { user }) => {
state.push(user);
}),
immerOn(updateUser, (state, { user, name }) => {
const index = state.findIndex(u => u.name === user.name);
state[index].name = name;
}),
immerOn(deleteUser, (state, { user }) => {
const index = state.findIndex(u => u.name === user.name);
state.splice(index, 1);
})
);
As you can see, the immerOn
method provides a more intuitive and direct way of updating the state. It allows you to modify the state directly instead of creating a new object with the spread operator.
You don’t have to refactor your app — mix and match !
No need to rewrite all of your reducers.
You can use immerOn in the places that are the most complicated.
You can combine the immerOn
method from the ngrx-immer
library with the on method from the @ngrx/store
library.
import { createReducer, on, immerOn } from 'ngrx-immer';
import { addUser, updateUser, deleteUser, togglePremium } from './user.actions';
export interface User {
name: string;
age: number;
premium: boolean;
}
export const initialState: User[] = [];
export const _userReducer = createReducer(
initialState,
on(addUser, (state, { user }) => [...state, user]),
immerOn(updateUser, (state, { user, name }) => {
const index = state.findIndex(u => u.name === user.name);
state[index].name = name;
}),
immerOn(deleteUser, (state, { user }) => {
const index = state.findIndex(u => u.name === user.name);
state.splice(index, 1);
}),
immerOn(togglePremium, (state, { user }) => {
const index = state.findIndex(u => u.name === user.name);
state[index].premium = !state[index].premium;
})
);
Almost no effort to use but makes a giant difference to your code readability and complexity.
Worthy of becoming part of the NgRx core library?
Do you think this should become a part of core NgRx library ? I vote that it should !
Why?
Installing multiple dependencies maintained outside of core libraries is always a risk of something that can stop being maintained.
Smaller lib, less maintainers = bigger risk.
And something as useful as this could come in bundle with NgRx.
Don’t agree ? Let’s discuss.
Sources
All credits go to fantastic Tim Deschryver — the author of this library.
Top comments (3)
I'm wondering why ngrx team didn't implement such approach earlier?
My question exactly, considering how createReducer in redux uses immer internally
I like it