If this helps demystify NgRx and makes it click, feel free to share it or give it a like!
Let me tell you about NgRx through the story of a garden. I’m the head gardener in charge of an estate. Managing this garden is like building state management in an app using NgRx. Let me explain.
The actions are my to-do list, each one a clear and specific task: “Plant roses,” “Trim the hedges,” or “Water the tulips.” These actions don’t actually do the gardening themselves—they’re just instructions.
Next, I pass the list to the reducers, which are like my team of gardening robots. The robots don’t make decisions; they’re programmed to follow the instructions from the to-do list and update the garden as needed. “Plant roses” means they add rose bushes to the garden bed, no questions asked. Every time they finish a task, the garden (state) gets updated to reflect the changes.
But what if something complicated comes up, like “Order exotic seeds from overseas”? That’s not something the robots can handle directly. This is where the effects come in—they’re like my off-site logistics team. When they see a special instruction, they handle the tricky part, like making the call to order seeds, and then they let the robots know when it’s done so they can update the garden accordingly.
So, in NgRx: Actions = the to-do list, Reducers = the robots updating the garden, and Effects = the logistics team handling side tasks. Together, they keep my garden flourishing and organized—just like state in an NgRx-powered app.
1. Actions: The To-Do List
Actions are simple objects that describe what needs to be done. In our garden analogy, these are like “Plant roses” or “Trim the hedges.”
// actions/garden.actions.ts
import { createAction, props } from '@ngrx/store';
// Action to plant roses
export const plantRoses = createAction('[Garden] Plant Roses', props<{ count: number }>());
// Action to water tulips
export const waterTulips = createAction('[Garden] Water Tulips');
Each action has a type ([Garden] Plant Roses
) and optional payload (count
).
2. Reducers: The Gardening Robots
Reducers define how the state (the garden) changes based on an action. They don’t make decisions—they just follow instructions.
// reducers/garden.reducer.ts
import { createReducer, on } from '@ngrx/store';
import { plantRoses, waterTulips } from '../actions/garden.actions';
export interface GardenState {
roses: number;
tulipsWatered: boolean;
}
const initialState: GardenState = {
roses: 0,
tulipsWatered: false,
};
export const gardenReducer = createReducer(
initialState,
on(plantRoses, (state, { count }) => ({
...state,
roses: state.roses + count,
})),
on(waterTulips, (state) => ({
...state,
tulipsWatered: true,
}))
);
Here, the reducer adds rose bushes or marks the tulips as watered based on the action. Notice how the state is immutable: we return a new object instead of modifying the original.
3. Effects: The Logistics Team
Effects handle asynchronous or external operations, like fetching seeds from an API. Once done, they dispatch new actions to update the state.
// effects/garden.effects.ts
import { createEffect, ofType, Actions } from '@ngrx/effects';
import { Injectable } from '@angular/core';
import { plantRoses } from '../actions/garden.actions';
import { map, switchMap } from 'rxjs/operators';
import { of } from 'rxjs'; // Simulating an API response
@Injectable()
export class GardenEffects {
constructor(private actions$: Actions) {}
orderSeeds$ = createEffect(() =>
this.actions$.pipe(
ofType(plantRoses),
switchMap((action) => {
console.log('Ordering seeds for', action.count, 'roses...');
// Simulating an API call
return of({ success: true }).pipe(
map(() => ({
type: '[Garden] Seeds Ordered', // New action to handle order completion
}))
);
})
)
);
}
Effects listen for specific actions (plantRoses
), handle side operations like API calls, and dispatch follow-up actions ([Garden] Seeds Ordered
).
Key Takeaways
- Actions: Represent clear instructions about what should happen in the app (or garden).
- Reducers: Update the state immutably based on the actions they handle.
- Effects: Handle side effects like API calls or complex logic and dispatch follow-up actions.
By separating concerns, NgRx makes large-scale state management predictable and testable. It’s like running a perfectly coordinated garden—one task at a time.