DEV Community

Cover image for NgRx to the Rescue
Lavkesh Dwivedi
Lavkesh Dwivedi

Posted on • Originally published at lavkesh.com on

NgRx to the Rescue

Originally published on lavkesh.com


When I'm working on a real Angular application, not just a toy project, I know that state management is going to become a pain. Components passing data up and down the component tree, services holding state that might drift out of sync, subscriptions scattered everywhere. Debugging weird race conditions where I have no idea what state the app is actually in is a nightmare.

That's where NgRx comes in, a state management library for Angular inspired by Redux. NgRx enforces a strict pattern: one central store holding all your application state, immutable and predictable. If you've written Redux in JavaScript, you'll recognize the concepts immediately. If not, NgRx's unidirectional data flow makes it easy to reason about how changes happen and when.

The core concepts of NgRx revolve around the Store, Actions, Reducers, Selectors, and Effects. The Store is your single source of truth, a single, immutable object containing all of your application state. Instead of scattering state across components and services, everything lives here.

Actions describe what happened, plain objects that say 'the user clicked this button' or 'the API returned data'. You don't change state directly, you dispatch actions, and the system responds to them. Reducers are pure functions that handle actions, taking the current state and an action, then returning a new state.

For example, I worked on a project where we used NgRx to manage a complex workflow with multiple steps, and the number of possible states was in the hundreds. By using NgRx, we were able to simplify the code and make it more predictable, with a significant reduction in bugs. We also used the Redux DevTools to debug issues, which allowed us to inspect every action dispatched and every state change, and jump to any point in the app's history.

Selectors let you grab specific slices of state from the store, memoized for performance, so your components only re-render when the data they actually care about changes. Effects handle side effects like HTTP requests, the bridge between your deterministic state management and the messy real world. In one project, we used the @ngrx/effects library to handle side effects, which allowed us to keep our components pure and focused on rendering the UI, while the effects handled the complexity of interacting with external systems.

Adopting NgRx might seem like adding complexity, but it pays dividends once your app scales beyond a certain point. I get predictability, because state only changes through reducers responding to actions. I can trace exactly how my app got into any given state, combined with Redux DevTools, I can time-travel through state changes, genuinely powerful for debugging. In terms of numbers, I've seen a reduction of around 30% in bugs related to state management, and a 25% reduction in the time spent debugging issues, after adopting NgRx.

NgRx's structure forces me to think about state organization, clear state slices and explicit change definitions. This structure pays off when I'm juggling dozens of features. Testing is also easier, since reducers are pure functions and effects can be tested in isolation. For instance, we used the Jest testing framework to write unit tests for our reducers and effects, which allowed us to test them in isolation and ensure they were working as expected.

In terms of trade-offs, one of the main downsides of using NgRx is the overhead of setting it up, which can be significant for small applications. However, for larger applications, the benefits of using NgRx far outweigh the costs. We've also found that using NgRx requires a significant shift in mindset, from thinking about components and services, to thinking about state and actions, which can take some time to get used to.

Finally, developer experience is better with NgRx. Redux DevTools let me inspect every action dispatched, every state change, and jump to any point in my app's history. This is invaluable for complex applications. We've also found that using NgRx makes it easier to onboard new developers, since the state management is centralized and predictable, making it easier for them to understand how the application works.

Getting NgRx running is straightforward. I start by installing NgRx and its core packages with npm or yarn. Then I define my state shape using TypeScript interfaces or classes, write my action creators, implement my reducers, define my effects, create selectors, and connect my components to the store.

Real talk, NgRx is powerful, but it's not a silver bullet. Smaller applications don't need it, and forcing it into a simple project will slow you down. Use it when state management becomes a pain point, when you have enough complexity that bugs are hard to track, when you need better developer experience for debugging.

Top comments (0)