DEV Community

Cover image for When and Why to Use REDUX NgRx in Angular
Dany Paredes
Dany Paredes

Posted on • Originally published at danywalls.com

When and Why to Use REDUX NgRx in Angular

In a previous article, I wrote about managing the state in Angular by using Services, but when the application increases in size and needs to track and handle a large state, it becomes a bit complex and not easy to manage. For example, when we have many components that need to react to those changes in the state.

At the beginning, using a service with a behavior subject is easy, but sometimes we start to encounter issues or side effects that make it hard to identify where the problem is occurring.

In our apps, we may handle a few types of state: sometimes persistent in the backend and coming from an API, in the URL state like http://myapp.com/users?id=2, or in a single component. So, when I really needs to use NgRx? 🤔

Do You Need Use NgRx ?

Well, NgRx is a great tool, but I don't use it for all my applications. Why? Before starting with NgRx, I ask myself some questions:

  • Is my app small and an MVP?

  • Do I need a state, and my components don't need to react to other state changes?

  • Do my teammates also know RxJS and Redux?

If you answer yes, then you don't need NgRx. Because NgRx requires a good knowledge of RxJS and involves writing boilerplate code. So, if you are working on something small or delivering your MVP, try building using Angular Services with Behavior or consider @ngrx/component-store as a good alternative.

But, if this is not your scenario and you want to continue with NgRx, then ask the next questions about your state.

  • Is it fetched by many components and services?

  • Is it persisted and change by others sources?

  • Is it changed by action from other sources?

  • Is it Needs to be ready for routes?

  • Is It Fetched by a side-effect ?

If most of that questions are true, then you need to use NgRx . Maybe the best way to figure out if really needs is with the following example. The Nba.com it have many interactions by the users also, needs to keep the state in the router, components, react change in the feed an more other points like:

  • The URL is linked with the state of the calendar and filters.

  • The calendars filter the data and the user can change it, but also update the URL.

  • the game feed of games must to react to changes in realtime.

1

In case the user have many interactions and the components must to react to them with multiple datasources. NgRx provides a set of libraries like @ngrx/store, @ngrx/effects, @ngrx/router-store, @ngrx/entity, @ngrx/component-store, @ngrx/signals and @ngrx/operators to help us build reactive and global state with isolation of side effects , also works entities collection integration with the Angular router and with a easy way to debug using a devtools.

NgRx makes it easy to organize and manage the state because it follows the REDUX pattern, so we have:

  • A Single source of truth store.

  • The State is read-only , only way to change is dispatching actions.

  • The reducers are pure functions responsible to change the State.

  • Provide Selectors to get specific slice of the state.

All those parts have a workflow to follow, and each one has its own responsibility during the process. So now, let's discuss the NgRx workflow.

NgRx Workflow

For example, in my Angular app, the home.component requires the user to accept the terms and conditions. After accepting, the user navigates to the about page. However, when they return to the home page, the terms and conditions checkbox is unchecked again 🙃.

Why doesn't my app keep the state?

Well, the home.component was destroyed and the state disappear, when the user move to about, and the home.component don't store the state in any place, I think it a perfect small case to solve with NgRx and to show how NgRx workflow works.

Yes, it is a easy case to solve with a Behavior Subject, but its the most easy way to show the NgRx workflow.

The NgRx workflow is based on Actions, Reducer, Store and Selectors (and Effect when needs), so the workflow must to be:

  • The component trigger an action [TermsCondition Page] Accept Terms Conditions

  • The reducer get the action trigger ,function getting a copy of the current state make the changes and update the state.

  • The component can read the change in the state by using a selectors or from the global store.

2

Setup Project

Instead of building an app from scratch, I prefer to provide you with a repo as the starting point. In my case, I'm using Angular with Standalone (no modules).

First, in the terminal, run the following code to clone the repo and install the dependencies.

git clone https://github.com/danywalls/start-with-ngrx.git
npm i
Enter fullscreen mode Exit fullscreen mode

Next, run ng serve to start the example application and navigate to http://localhost:4200. On the home page, check the checkbox. Then, click on "About" and go back to "Home." You will see that the checkbox is no longer checked.

3

We have the scenario ready, so it's time to fix it using NgRx! ⚒️

Configure NgRx

Next install ngrx by running the command npm i @ngrx/store :

npm i @ngrx/store
Enter fullscreen mode Exit fullscreen mode

Open the app.config.ts file. In the providers section, use the provideStore function to configure the global state. It expects the state and reducer, but since we don't have the reducer ready, initialize it with an empty function for now.

import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import { provideStore } from '@ngrx/store';

export const appConfig = {
  providers: [provideRouter(routes), provideStore()],
};
Enter fullscreen mode Exit fullscreen mode

Next, we need to set up the state for the home page. Create a new directory: pages/home/home.state.ts. Inside, create an interface for the HomeState and define the initialState.

the state is a object with the properties to expose by the state.

export interface HomeState {
  acceptTerms: boolean
}
const initialState: HomeState = {
  acceptTerms: false,
}
Enter fullscreen mode Exit fullscreen mode

We have the state ready, so let's create the reducer in pages/home/state/home.reducer.ts. Import the createReducer function with the initialState and import the on function.

Import the createAction function to define the action. Pass the action name [Home Page] Accept Terms and, as a parameter, the state.

Use the spread operator (...) to get the current value of state and update the acceptTerms property.

The final code looks like:

import { createAction, createReducer, on } from '@ngrx/store';
import { initialState } from './home.state';

export const homeReducer = createReducer(
  initialState,
  on(createAction('[Home Page] Accept Terms'), (state) => ({
    ...state,
    acceptTerms: !state.acceptTerms,
  })),
);
Enter fullscreen mode Exit fullscreen mode

We are now ready to initialize the store. Open the app.config.ts file again and add the home slice object to the reducer using the homeReducer.

Another option is use provideState

import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
import {provideStore, StoreModule} from "@ngrx/store";
import {homeReducer} from "./pages/home/state/home.reducer";

export const appConfig = {
  providers: [provideRouter(routes),
   provideStore(
     {
       home: homeReducer,
     }
   )
  ]
};
Enter fullscreen mode Exit fullscreen mode

Using NgRx

We are in the final steps. First, open the home.component.ts file and inject the store. Next, store $acceptTerms from the store. For this article, I want to skip the selector, so I will subscribe directly to the store using select and transform the observable to a signal using the toSignal function to avoid subscribing with the async pipe.

We make a small change in the onChange method. Using the store, we dispatch the action to trigger the update from the reducer.

The final code looks like this:

export class HomeComponent {
  private _store = inject(Store);
  public $acceptTerms = toSignal(this._store.select((state) => state.home.acceptTerms))

  onChange() {
    this._store.dispatch({
      type: '[Home Page] Accept Terms'
    })
  }
}
Enter fullscreen mode Exit fullscreen mode

In the template, update the logic to use the $acceptTerms signal by reading the value with ().

@if(!$acceptTerms()) {
    <h3>Do you want to use NgRx?</h3>
    <input (change)="onChange()" [value]="$acceptTerms()" type="checkbox"  >
} @else {
   <h2>Thanks for love NgRx🎉🥳</h2>
}
Enter fullscreen mode Exit fullscreen mode

Save the changes and ta-da! 🎉🎉🎉 NgRx is now set up, and the state is integrated into our app!

4

Conclusion

We learned how easy it is to create a state, action, and reducer, and read the state directly using the store. However, for a small application, it might feel like too much boilerplate, but it's a great starting point with NgRx.

We also have examples of when to use NgRx over Angular Services. You can use the checklist to decide if NgRx is necessary.

Finally, I learned with a basic practical example how to configure NgRx to manage state. This includes setting up the store, creating state and reducers, and integrating NgRx into a component to easily maintain state across navigation.

Top comments (1)

Collapse
 
jangelodev profile image
João Angelo

Hi Dany Paredes,
Top, very nice and helpful !
Thanks for sharing.