myHotTake

Services vs. NgRx: Which Should You Choose for State?

If this story makes state management finally click, feel free to give it a like or share—it might help someone else out too!


Think of state management like running a daycare for kids’ toys. Stick with me.

When I started, I ran my daycare the simple way—just me and my backpack. Every kid brought me their toys to hold, and when they wanted something back, I’d dig around to find it. This worked when there were only a handful of kids. In JavaScript terms, that’s like using services for state management: straightforward, fast to set up, and great when the toy count is low.

But then, the daycare grew. More kids. More toys. Suddenly, the backpack was overflowing. Toys were getting lost, and the kids started fighting about who had what. It was chaos! That’s when I decided to implement a “toy cabinet system,” color-coded and labeled, where every kid’s toys were stored in their designated section. Now, whenever a toy was needed, the kids knew exactly where to find it—or I could grab it for them quickly. That’s NgRx: a structured, predictable system for managing complex states.

The difference is simple. If my daycare is small, I don’t need a fancy cabinet; my backpack works fine. But as the daycare scales, the cabinet ensures everything is organized, and I don’t lose my mind.

So, when choosing between services and NgRx, I ask myself: how many toys am I managing, and how much chaos can I handle?


Using Services (The Backpack)

If you have a simple app, a service can manage your state just fine. Let’s say you’re tracking a single list of toys:

@Injectable({
  providedIn: 'root',
})
export class ToyService {
  private toys: string[] = [];

  getToys(): string[] {
    return this.toys;
  }

  addToy(toy: string): void {
    this.toys.push(toy);
  }
}

You can inject this ToyService into components, call addToy to update the state, and getToys to retrieve it. Easy, lightweight, and minimal setup.

Using NgRx (The Toy Cabinet)

When the daycare (or app) grows, you might have multiple states to track: toys, snack schedules, attendance, etc. Here’s how you’d manage the toy state with NgRx:

Define Actions:

export const addToy = createAction('[Toy] Add', props<{ toy: string }>());
export const loadToys = createAction('[Toy] Load');

Define a Reducer:

export interface ToyState {
  toys: string[];
}

const initialState: ToyState = {
  toys: [],
};

export const toyReducer = createReducer(
  initialState,
  on(addToy, (state, { toy }) => ({
    ...state,
    toys: [...state.toys, toy],
  }))
);

Set Up a Selector:

export const selectToys = (state: { toys: ToyState }) => state.toys.toys;

Use It in a Component:

@Component({
  selector: 'app-toy-list',
  template: `
    <ul>
      <li *ngFor="let toy of toys$ | async">{{ toy }}</li>
    </ul>
    <button (click)="addToy('Teddy Bear')">Add Toy</button>
  `,
})
export class ToyListComponent {
  toys$ = this.store.select(selectToys);

  constructor(private store: Store) {}

  addToy(toy: string): void {
    this.store.dispatch(addToy({ toy }));
  }
}

With NgRx, everything is organized: actions define what can happen, reducers update the state, and selectors make it easy to fetch data. The toy cabinet is fully in place.


Key Takeaways

  1. Services (Backpack): Great for small apps with simple state. Quick, lightweight, and easy to implement.
  • Use services when managing a single slice of state or when you don’t need global state synchronization.
  1. NgRx (Toy Cabinet): Ideal for complex apps where multiple states interact or need to be shared.
  • Use NgRx when your app scales and you need predictability, immutability, and testability.
  1. Transition Wisely: Start with a backpack (services) and upgrade to a toy cabinet (NgRx) as your app grows.

By matching the tool to the scale of your app, you’ll keep things simple and efficient—just like a well-run daycare. 🎒 vs. 🗄️

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *