Building complex web applications is hard. Building complex web applications that you can still understand once you’ve built them is even harder.
Following established patterns for common problems like state management can speed up development time while greatly improving the readability of your projects.
NgRx is an excellent choice when implementing a battle tested design for your projects. NgRx is an Angular library which implements the Flux pattern, a popular solution for managing state in complex applications.
Not sure if NgRx is the right fit for your project? We’re here for you!
In Part One of this series, we’ll review common state-management problems in Angular applications and how to solve them using NgRx. In Part Two we’ll get hands-on and build an image gallery using NgRx to manage application state and handle image data from a free API.
What is Application State?
What is “state” in an application? Simply put, state is the data you “care” about. Imagine there’s a button on a page. Now imagine you took a "snapshot" of a user interacting with that page. Do you care whether or not the user clicked that button? Does clicking the button trigger a change? Perhaps the button fetches a set of images, do you need to know if we’ve fetched those images yet?
If the answer to those questions is “yes”, then that button’s state (clicked or not clicked), and its corresponding effects (images) are all part of your application “state”.
Application state can be tricky to understand; I personally struggled with this term when I started developing, but having a good grasp of state management can help you build more maintainable applications. Stick with it! You probably understand this concept better than you think, and these blog posts should help!
What is NgRx?
NgRx follows the “Flux” style of state management. The core concept of Flux is a single source-of-truth for your application state called the Store. The Store holds all information related to your application state and is available throughout your application.
The integrity of this all-important source-of-truth is ensured by preventing direct access to the Store. Instead, stateful data from the Store is read via Selectors and is modified by dispatching Actions. These controlled “in and out” gates reduce the likelihood of components making unintentional changes to shared state and provide a central location for processing changes.
NgRx is opinionated. This is a simple but easy-to-underappreciate feature of NgRx. Many of the benefits provided by NgRx can be approximated without the library, but NgRx makes it easy. It follows a specific design pattern and makes decisions for you on how to structure an application.
Imagine you have a whole team of developers, each with their own differing opinions and experiences around state management. Identifying, designing and implementing a solution can quickly become a challenge. This often leads to inconsistent results, with the biggest design factor simply being who writes the code.
NgRx makes decisions for you. Once you’ve selected it as the right tool for your project, NgRx provides a clear structure to which you and your team can adhere.
What problems can we solve with NgRx? Let’s take a look!
Sharing Data between Distant Components
Different components often access the same data. If the components are on the same level, data can easily be passed between them. However, the further apart they get, the harder it is to bridge the gap. Services can freely pass data around the component tree, but relying on them can make your components harder to reuse.
Like services, they can share data across your component tree. Selectors’ primary benefit is predictability: NgRx provides the “select” function for invoking them, and they always return data as an Observable.
Selectors also ensure you are receiving data whose stateful side-effects have already been managed. Two components sharing data might be unaware of each other’s changes to that data, and neither component Inputs nor a service guarantee that those changes will be correctly propagated. NgRx guarantees that two components using the same Selector will always have the same data.
Implementing Derived State
Derived State: a tricky part of every developer’s existence. Data changes in your application, and that change triggers a subsequent change. We did not request this second state change directly, how do we understand where it comes from?
NgRx’s combination of Reducers and Actions bring clarity to this potential quagmire. In NgRx, data flow is unidirectional: it always comes from the Store. If we want to change stateful data, we have to make those changes in the Store.
How do we make changes to the Store? By dispatching an Action! Each Action is only processed by a Reducer, and Reducers are the only way we can update the Store. Unsure how a change is being made? In an NgRx application, simply find the originating Action and you’ll find the change in its Reducer.
This brings the added benefit of reusability: the Action/Reducer combination makes your state changes source agnostic. This is especially useful when leveraging real-time server connections like Web Sockets. An NgRx application can make a change via a button or from a Web Socket event, both changes will dispatch the same Action and update application state via the same Reducer.
When is NgRx the right fit?
NgRx is an excellent tool, but it’s not the right tool for every project. If your application state fits any of these scenarios, NgRx might be a good choice:
- Shared: state that is accessed by many components and services.
- Hydrated: state that is persisted and rehydrated from external storage.
- Available: state that needs to be available when re-entering routes.
- Retrieved: state that must be retrieved with a side-effect.
- Impacted: state that is impacted by actions from other sources.
Read more on the NgRx site.
What does this mean to you? Here are a few examples of apps that might not see big benefits from NgRx.
Small projects: an application with only a handful of components probably doesn’t need NgRx. The framework adds a moderate amount of complexity, which shouldn’t be disproportionate to the function of the application itself. NgRx’s structure also results in multiple state-oriented files, which may begin to outnumber your components in a small project!
Projects with isolated components: falling into the “shared” and “impacted” categories above, an app whose components rarely affect each other will see reduced benefits from NgRx. The framework shines when sharing state between entities and consolidating changes from multiple sources.
Offering impactful de-coupling and a highly scalable, time-tested approach to state management, NgRx is a stellar choice for building Angular apps.
Still not sure if NgRx is the right choice for your project? We're happy to help! Shoot us an email at firstname.lastname@example.org.