Post

07. State Management in Angular

🚀 Master the art of state management in your applications! Learn NgRx basics, handle side effects effectively, explore alternative tools, and build robust, scalable apps. 💡

07. State Management in Angular

What we will learn in this post?

  • 👉 Introduction to State Management
  • 👉 NgRx Basics
  • 👉 Managing Side Effects
  • 👉 Alternative State Management Tools
  • 👉 Conclusion!

State Management in Angular: Keeping Your App Organized 📦

Imagine building a house—you wouldn’t just throw bricks together randomly, right? Similarly, in complex Angular apps, managing application data (the “state”) effectively is crucial for maintainability and scalability. Poor state management leads to messy, unpredictable, and hard-to-debug code.

Why Use State Management? 🤔

  • Centralized Data: Keeps all your app’s data in one place, making it easier to access and update.
  • Improved Performance: Avoids unnecessary re-renders and improves the user experience.
  • Testability: Easier to write unit and integration tests for your application logic.
  • Code Reusability: Facilitates code reusability across different components.

NgRx ngrx

NgRx uses a predictable state container powered by RxJS Observables. It follows the principles of Redux, offering a structured approach. Think of it as a central store for your data that components can subscribe to for updates.

Akita 🐿️

Akita is a state management library that provides a more streamlined and developer-friendly experience compared to NgRx. It uses a simpler API and is easier to learn.

Real-World Examples ✨

User Authentication: Imagine a login form. NgRx or Akita would store the user’s authentication status (logged in/out, user details) in the central store. Components can then subscribe to changes in this state to display appropriate UI elements.

Shopping Cart: In an e-commerce app, the shopping cart’s contents (items, quantities, total price) are managed in the state. Adding or removing items updates the central store, automatically reflecting changes in all related components (cart display, checkout page).

More on NgRx More on Akita

graph LR
    A["📦 Component 1"] --> B["🗂️ NgRx Store"];
    C["📦 Component 2"] --> B;
    B --> D{"🔄 Data Changes"};
    D --> A;
    D --> C;

    class A componentStyle;
    class B storeStyle;
    class C componentStyle;
    class D dataChangesStyle;

    classDef componentStyle fill:#90CAF9,stroke:#1E88E5,color:#000000,font-size:14px,stroke-width:2px,rx:10,shadow:3px;
    classDef storeStyle fill:#FFD54F,stroke:#FBC02D,color:#000000,font-size:14px,stroke-width:2px,rx:10,shadow:3px;
    classDef dataChangesStyle fill:#A5D6A7,stroke:#388E3C,color:#000000,font-size:14px,stroke-width:2px,rx:10,shadow:3px;

This diagram shows how components interact with the central NgRx store. Similar interactions would happen with Akita.

Understanding NgRx: A Simple Guide 🛍️

NgRx is a state management library for Angular applications. Think of it as a central hub that keeps track of all your app’s data, making it easier to manage and update. It uses a predictable, unidirectional data flow, making debugging and understanding your app’s behavior much simpler.

Core Concepts 🧠

Actions ➡️

Actions are simply messages that describe what happened in your application. For example, ADD_PRODUCT, REMOVE_PRODUCT, or UPDATE_PREFERENCES. They’re plain JavaScript objects with a type property and any payload data.

Reducers ⚙️

Reducers are pure functions that take the current application state and an action as input, and return a new state. They’re responsible for how the state changes in response to actions. Crucially, they never modify the existing state directly; they create a copy.

Selectors 🔎

Selectors are functions that retrieve specific parts of the application state. They act as a clean way to access data from the store, making your components independent of the store’s internal structure.

Example: Managing Product List 🛒

Let’s say we’re building an e-commerce app and want to manage a product list using NgRx.

  • Action: ADD_PRODUCT with a payload containing product details.
  • Reducer: Takes the current productList array and the ADD_PRODUCT action. It creates a new array by adding the product from the action’s payload.
  • Selector: A function that retrieves the entire productList or specific products based on criteria (e.g., category).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Action
export const addProduct = (product) => ({
  type: "ADD_PRODUCT",
  payload: product,
})

// Reducer
export function productReducer(state = [], action) {
  switch (action.type) {
    case "ADD_PRODUCT":
      return [...state, action.payload]
    default:
      return state
  }
}

// Selector
export const selectProductList = (state) => state.productList

Learn more about NgRx

graph LR
A["📦 Component"] --> B["⚡ Dispatch Action"];
B --> C{"🔧 Reducer"};
C --> D["🗂️ Store"];
D --> E["🔍 Selector"];
E --> A;

class A componentStyle;
class B actionStyle;
class C reducerStyle;
class D storeStyle;
class E selectorStyle;

classDef componentStyle fill:#90CAF9,stroke:#1E88E5,color:#000000,font-size:14px,stroke-width:2px,rx:10,shadow:3px;
classDef actionStyle fill:#FFD54F,stroke:#FBC02D,color:#000000,font-size:14px,stroke-width:2px,rx:10,shadow:3px;
classDef reducerStyle fill:#A5D6A7,stroke:#388E3C,color:#000000,font-size:14px,stroke-width:2px,rx:10,shadow:3px;
classDef storeStyle fill:#FFCDD2,stroke:#E53935,color:#000000,font-size:14px,stroke-width:2px,rx:10,shadow:3px;
classDef selectorStyle fill:#FFE082,stroke:#F9A825,color:#000000,font-size:14px,stroke-width:2px,rx:10,shadow:3px;

This diagram shows the flow of data: A component dispatches an action, the reducer updates the store, and selectors access the updated data.

Remember, NgRx promotes a predictable and manageable way to handle data in your Angular application. By following this structure, you can build more robust and scalable applications.

Handling Side Effects in NgRx with Effects 💡

NgRx Effects are a powerful way to manage side effects in your Angular applications. Side effects are anything that interacts with the outside world, like making API calls or interacting with the browser. Instead of cluttering your components, Effects handle these operations, keeping your application clean and predictable.

HTTP Requests with Effects 🚀

Let’s say you need to fetch data from an API. Here’s how you’d do it with an Effect:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import { Injectable } from "@angular/core"
import { Actions, createEffect, ofType } from "@ngrx/effects"
import { of } from "rxjs"
import { map, mergeMap, catchError } from "rxjs/operators"
import * as UsersActions from "./users.actions" // Your actions
import { UsersService } from "./users.service" // Your service

@Injectable()
export class UserEffects {
  loadUsers$ = createEffect(() =>
    this.actions$.pipe(
      ofType(UsersActions.loadUsers),
      mergeMap(() =>
        this.usersService.getUsers().pipe(
          map((users) => UsersActions.loadUsersSuccess({ users })),
          catchError((error) => of(UsersActions.loadUsersFailure({ error }))),
        ),
      ),
    ),
  )

  constructor(
    private actions$: Actions,
    private usersService: UsersService,
  ) {}
}

Explanation ✍️

  • We use createEffect to define an effect that listens for UsersActions.loadUsers.
  • mergeMap makes the API call (this.usersService.getUsers()).
  • map transforms the successful response into a success action (UsersActions.loadUsersSuccess).
  • catchError handles errors and dispatches a failure action.

Other Asynchronous Operations ⏳

Effects aren’t limited to HTTP requests. You can use them for:

  • Timers: Dispatch an action after a certain delay.
  • WebSockets: Handle incoming messages from a WebSocket connection.
  • User Input: Process user input asynchronously (e.g., debouncing a search input).

Example: Debouncing User Input ⌨️

You could use a debounceTime operator to delay the processing of user input until a certain time has elapsed since the last keystroke.

graph LR
    A["🖊️ User Input"] --> B{"⏳ debounceTime(300ms)"};
    B -- "🕒 After 300ms without input" --> C["⚡ Dispatch Action"];
    B -- "🔄 Input within 300ms" --> A;

    class A userInputStyle;
    class B debounceStyle;
    class C actionDispatchStyle;

    classDef userInputStyle fill:#90CAF9,stroke:#1E88E5,color:#000000,font-size:14px,stroke-width:2px,rx:10,shadow:3px;
    classDef debounceStyle fill:#FFE082,stroke:#F9A825,color:#000000,font-size:14px,stroke-width:2px,rx:10,shadow:3px;
    classDef actionDispatchStyle fill:#A5D6A7,stroke:#388E3C,color:#000000,font-size:14px,stroke-width:2px,rx:10,shadow:3px;


Key Takeaways 👍

  • Keep Components Clean: Effects handle side effects, keeping components focused on presentation.
  • Improved Testability: Effects are easily testable due to their isolated nature.
  • Predictable State Changes: Actions drive state changes, making debugging easier.

For more in-depth information and advanced techniques, refer to the official NgRx documentation: https://ngrx.io/guide/effects

Remember to install the necessary NgRx packages: @ngrx/store and @ngrx/effects. Happy coding! 🎉

NgRx Alternatives: State Management Made Easy 🤔

NgRx is a powerful state management library for Angular, but it’s not the only game in town! Let’s explore some alternatives.

Akita 🥋

Akita is a state management library inspired by NgRx but with a simpler, more intuitive API. It uses RxJS under the hood, but simplifies the boilerplate.

Pros 👍

  • Easier learning curve than NgRx.
  • Cleaner code and less boilerplate.
  • Good performance.

Cons 👎

  • Smaller community than NgRx.
  • Fewer readily available resources.

When to use Akita: Mid-sized to large applications where you need a structured approach to state management but don’t want the complexity of NgRx. Great for teams preferring a simpler workflow.

Plain RxJS 🌊

For smaller applications, you might find that RxJS itself is sufficient for state management. You can leverage its power of observables and operators to manage application state directly within your components.

Pros 👍

  • Lightweight and minimal overhead.
  • No external dependencies beyond RxJS.
  • Already familiar if you’re using Angular.

Cons 👎

  • Can become cumbersome and difficult to maintain in larger applications.
  • State management logic can be scattered throughout your components.

When to use plain RxJS: Small to medium-sized applications, or for managing very simple application state. This is often preferable for handling local component state.

Choosing the Right Tool 🎯

  • Small apps (local state): Plain RxJS or even simple component-level state management might suffice.
  • Medium-sized apps (shared state): Akita offers a good balance of structure and simplicity.
  • Large enterprise apps (complex state): NgRx provides the scalability and features needed for complex applications.

Remember to consider your team’s expertise and project requirements when making your decision.

More on Akita More on RxJS More on NgRx


Flowchart (Conceptual):

graph TD
    A["📊 Project Size"] --> B{"🧐 Small?"};
    B -- "✔️ Yes" --> C["✨ Plain RxJS"];
    B -- "❌ No" --> D{"🤔 Complex State?"};
    D -- "✔️ Yes" --> E["⚙️ NgRx"];
    D -- "❌ No" --> F["🌟 Akita"];

    class A projectSizeStyle;
    class B smallStyle;
    class C plainRxJSStyle;
    class D complexStateStyle;
    class E ngrxStyle;
    class F akitaStyle;

    classDef projectSizeStyle fill:#90CAF9,stroke:#1E88E5,color:#000000,font-size:14px,stroke-width:2px,rx:10,shadow:3px;
    classDef smallStyle fill:#FFE082,stroke:#F9A825,color:#000000,font-size:14px,stroke-width:2px,rx:10,shadow:3px;
    classDef plainRxJSStyle fill:#A5D6A7,stroke:#388E3C,color:#000000,font-size:14px,stroke-width:2px,rx:10,shadow:3px;
    classDef complexStateStyle fill:#FFD54F,stroke:#F57F17,color:#000000,font-size:14px,stroke-width:2px,rx:10,shadow:3px;
    classDef ngrxStyle fill:#FFCDD2,stroke:#E53935,color:#000000,font-size:14px,stroke-width:2px,rx:10,shadow:3px;
    classDef akitaStyle fill:#CE93D8,stroke:#8E24AA,color:#000000,font-size:14px,stroke-width:2px,rx:10,shadow:3px;

Conclusion

So there you have it! We’ve covered a lot of ground today, and hopefully, you found this information helpful and interesting. 😊 But the conversation doesn’t end here! We’d love to hear your thoughts, feedback, and any suggestions you might have. What are your experiences with [topic of blog]? What did you find most insightful? Let us know in the comments below! 👇 We’re eager to continue the discussion and learn from your perspectives. Happy commenting! 🎉

This post is licensed under CC BY 4.0 by the author.