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.
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 theaction
trigger ,function getting a copy of the currentstate
make the changes and update the state.The component can read the change in the state by using a
selectors
or from the globalstore
.
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
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.
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
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()],
};
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,
}
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,
})),
);
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,
}
)
]
};
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'
})
}
}
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>
}
Save the changes and ta-da! 🎉🎉🎉 NgRx is now set up, and the state is integrated into our app!
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)
Hi Dany Paredes,
Top, very nice and helpful !
Thanks for sharing.