myHotTake

Tag: NgRx async state

  • NgRx Async Updates Explained: What’s the Process?

    If you find this explanation helpful or just love creative tech analogies, feel free to share or drop a like! Let’s dive in.


    I’m running a typical coffee shop, and my specialty is making complex, custom coffee orders. Now, orders come flying in all day from all over—online, walk-ins, drive-thru. My baristas, bless them, can’t keep up in real-time. So, I set up a genius solution: a chalkboard order board that everyone can see.

    Each time someone places an order, it’s added to the chalkboard. That’s like dispatching an action in NgRx. It’s just the order—a clear declaration of what someone wants. No coffee’s made yet; we just know what needs to happen.

    Now, I have a highly focused team member—a “state manager.” Their job is to see every order on the board and update the status board where we track all orders—what’s being made, what’s ready, and what’s delivered. This status board is our store—the single source of truth that anyone in the shop can check to know what’s going on.

    But here’s where it gets cool. Some orders, like fancy espresso drinks, take longer than others. Instead of waiting for each drink to finish before updating the status board (that would cause chaos!), my state manager uses a timer to handle asynchronous updates. When an espresso order is complete, the machine pings them, and they update the board without missing a beat. That’s where effects come in—they handle side tasks like brewing coffee while the state stays neat and tidy.

    With this system, no one’s overwhelmed, and no order is lost. I can confidently handle hundreds of orders a day without missing a beat. NgRx, like my coffee shop, thrives because the state is centralized, asynchronous tasks are offloaded, and everyone knows exactly where to look to find the truth about any order.


    The Order (Actions)

    Actions in NgRx represent those chalkboard orders—what we want to happen. Let’s define an action for placing a coffee order:

    import { createAction, props } from '@ngrx/store';
    
    // Customer places an order
    export const placeOrder = createAction(
      '[Coffee Shop] Place Order',
      props<{ orderId: string; drink: string }>()
    );

    This placeOrder action tells the system that a new drink order is in. Just like adding to the chalkboard, this doesn’t brew the coffee yet—it just signals intent.


    The Status Board (State and Reducers)

    The state is our single source of truth, tracking every order’s status. The reducer updates this state based on the actions:

    import { createReducer, on } from '@ngrx/store';
    import { placeOrder } from './coffee.actions';
    
    // Initial state for the coffee shop
    export interface CoffeeState {
      orders: { [key: string]: string }; // Mapping orderId to its status
    }
    
    export const initialState: CoffeeState = {
      orders: {}
    };
    
    export const coffeeReducer = createReducer(
      initialState,
      // Update state when an order is placed
      on(placeOrder, (state, { orderId, drink }) => ({
        ...state,
        orders: { ...state.orders, [orderId]: `Preparing ${drink}` }
      }))
    );

    Now, whenever placeOrder is dispatched, the reducer updates the status board (our state).


    The Barista (Effects for Async Tasks)

    Not every drink is instant—our espresso machine needs time. Effects manage these asynchronous tasks.

    import { createEffect, ofType, Actions } from '@ngrx/effects';
    import { Injectable } from '@angular/core';
    import { of } from 'rxjs';
    import { delay, map } from 'rxjs/operators';
    import { placeOrder } from './coffee.actions';
    
    @Injectable()
    export class CoffeeEffects {
      constructor(private actions$: Actions) {}
    
      makeCoffee$ = createEffect(() =>
        this.actions$.pipe(
          ofType(placeOrder),
          delay(3000), // Simulate brewing time
          map(({ orderId }) => ({
            type: '[Coffee Shop] Order Ready',
            orderId
          }))
        )
      );
    }

    Here, the effect listens for placeOrder, simulates a 3-second brewing delay, and then dispatches a new action when the coffee is ready.


    Final Touches: Selectors and Key Takeaways

    Selectors allow us to fetch specific data from the state, like the status of a specific order:

    import { createSelector } from '@ngrx/store';
    
    export const selectOrders = (state: CoffeeState) => state.orders;
    
    export const selectOrderStatus = (orderId: string) =>
      createSelector(selectOrders, (orders) => orders[orderId]);

    With selectors, components can stay focused on just the information they need, keeping everything efficient and clean.


    Key Takeaways

    1. Actions are the chalkboard orders: they declare intent but don’t execute logic.
    2. Reducers update the state (status board) based on those actions.
    3. Effects handle asynchronous processes (like brewing coffee) without clogging up the main flow.
    4. Selectors fetch just the right slice of state for a given purpose.