DEV Community

Cover image for Make your Angular NgRx reducers an eye candy 🍬 using this fantastic library
Daniel Glejzner for This is Angular

Posted on

Make your Angular NgRx reducers an eye candy 🍬 using this fantastic library

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.

Immer

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)
  )
);
Enter fullscreen mode Exit fullscreen mode

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);
  })
);
Enter fullscreen mode Exit fullscreen mode

As you can see, the immerOnmethod 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;
  })
);
Enter fullscreen mode Exit fullscreen mode

Almost no effort to use but makes a giant difference to your code readability and complexity.

rxjs

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.

https://github.com/timdeschryver/ngrx-immer

Top comments (3)

Collapse
 
draylegend profile image
Vladimir Drayling

I'm wondering why ngrx team didn't implement such approach earlier?

Collapse
 
akuoko_konadu profile image
Konadu Akwasi Akuoko

My question exactly, considering how createReducer in redux uses immer internally

Collapse
 
moroccangeek profile image
Hamza SABER

I like it