State management in Single Page Applications

v.18

v.18

Fabio Biondi
Google Developer Expert
Microsoft MVP
# What is a State?
In the context of frontend development, the term "state" refers to the current condition or status of a component or the entire application at a given point in time.
It's a representation of all the dynamic data that can change over time as a user interacts with the application.
This data may include:
1) User Interface (UI) status
- Which component is currently displayed.
- The loading (state) of a resource.
- The expansion or collapse status of a menu.
- If a Modal is opened or closed
- Information about currently authenticated users.
- Users' preferences.
- Details of the users' profiles.
- Information retrieved from an API backend.
- Resource data (products, orders, items).
- Configurations of the application.
- Current Language or Theme
- User input into forms.
- Values selected in the search filters.
- Data not yet saved on a server.
# State Managers: when and why?
In the evolving landscape of web development, managing the state of an application efficiently has become increasingly critical.
As applications grow in complexity it becomes difficult to manage a consistent state and the entire data flow with multiple side effects and async operations.
This is where state management comes into play, and it's why discussing state managers, such as NGRX, is essential.
# So when State Managers are useful?
There are typically two scenarios where life gets messed up easily:
1. Complex Global State Management
A common scenario is when the application has a complex data flow, with multiple components needing to share and update the same state or keep in sync multiple states.
Often it's very hard to keep tracks of what is happening behind the scenes, the right sequence of the operations or how and when states are updates.
The more complex the application becomes, the more it will become a fragile castle that can easily break.
In large applications with hundred of components, services, side effects (i.e. http requests) and routes, a state manager can help keep the state organized and manageable.
2. Complex Local State Management
But even when we talk about local states, a state manager can help for the same reasons described above.
Imagine a page where there are dozens of components and states to keep in sync such as a no-code tool, a photo editor, or a "Fintech" dashboard with several panels, features and tons of data to display
A state management facilitates communication between components, helps manage data flow, and ensures that the state of the application remains consistent.
# Cheatsheet PDF
# The Problem with Services and Dependency Injection
In Angular, we can easily manage global states using the Dependency Injection (D.I.) system..
We can create a Service, a class containing signals (or Observables) that can be shared across the whole application by the D.I.
The problem arises when the states to manage increase, there are sequential or parallel operations, side effects to manage, dozens of routes and the application becomes more complicated daily.
So we start creating a service to handle a list of Products, another service to store the filters applied by the user, another one to hold the UI state, and so on. Dozens and Hundreds of services to handle everything.
And several components can inject them to read and update the states.
As an Angular Developer, you've probably already had this problem, so I'm not telling you anything new, but if you've never had to manage such complex situations, I assure you that project management and understanding all its phases starts to get complicated and out of control.
# Don't abuse of DI
While using multiple services might seem manageable initially, it can quickly lead to chaos as the application grows.
Let's summarize some of the problems we can encounter in developing applications that abuse the Angular injection system:
lbdcol
1. Inconsistent State Management
When we have multiple services managing different parts of the application state, it becomes challenging to keep the state consistent. Each service may have its own way of handling data, leading to potential conflicts and inconsistencies.
2. State Synchronization Issues
With multiple services, synchronizing state changes across different components and routes becomes complex. If one service updates a piece of state, other services or components relying on the same data might not get updated, leading to outdated or stale data being used.
Multiple services increase the complexity of the application. Developers need to understand the role and responsibility of each service, how they interact each other, and how state changes propagate through the application.
A service can read or write data from another service. When there are dozens or hundreds of services it's easy to create circular dependencies (A -> B -> C -> A) which should be avoided and generate problems in maintainability.
When issues arise, debugging becomes more difficult with multiple services. Tracing the flow of data and identifying where things went wrong requires navigating through several layers of services and understanding their interactions.
The state application flow is very hard to follow when you try to debug or simply to understand what's happening in a specific phase of the application lifecycle.
7. Higher Risk of Memory Leaks
Injecting multiple services in various components can increase the risk of memory leaks. If services are not properly managed and disposed of, they can linger in memory, consuming resources and potentially causing performance issues.
Multiple services might lead to unnecessary network requests or state updates. Without a centralized state management system, it’s harder to optimize performance and avoid redundant operations.
NGRX offers a robust solution by centralizing state management, ensuring consistency, predictability, and maintainability.
# Cheatsheet PDF