In NgRx, what is an action, and how does a component define one with createAction and send it to the Store?
answer
- a plain object with a type
- describes something that happened
- [Source] Event in the type string
- createAction plus props<T>()
- call the creator, then dispatch
basics
~20 sAn NgRx action is a plain object whose type string names an event, such as '[Catalogue Page] Opened'. You declare an action creator with createAction and props, call it to build the object, and pass that object to Store.dispatch.
solid answer
~40 sAn action is a plain object with a `type` string, plus optional payload, that reports an event to the store, for example `{ type: '[Catalogue Page] Genre Changed', genre: 'poetry' }`. I declare it with `createAction('[Catalogue Page] Genre Changed', props<{ genre: string }>())`, which gives me a typed action creator. In the component I `inject(Store)` and call `this.store.dispatch(genreChanged({ genre }))`, calling the creator first. `dispatch` returns `void`: reducers compute the next state, effects run side effects, and the component reads the result through selectors. The type follows `[Source] Event`, so it says where the event came from and what happened, not which function to run.
code
ts · 17 linesimport { createAction, props } from '@ngrx/store';
export const cataloguePageOpened = createAction('[Catalogue Page] Opened');
export const genreChanged = createAction(
'[Catalogue Page] Genre Changed',
props<{ genre: string }>()
);
export const wishlistToggled = createAction(
'[Catalogue Page] Wishlist Toggled',
(bookId: string) => ({ bookId })
);
// genreChanged({ genre: 'poetry' })
// -> { genre: 'poetry', type: '[Catalogue Page] Genre Changed' }
// genreChanged.type === '[Catalogue Page] Genre Changed'go deeper
Recall that an action is a plain object with a type string, built by calling a createAction creator and passed to store.dispatch. Show the props<T>() form for a payload.
Explain the three createAction forms, why the creator carries a read-only type property, and why dispatch returns nothing: state comes back through selectors.
Show you treat actions as events from a named source, not commands, and that you would catch uncalled creators and hand-written literals in review or lint.
Frame actions as the store's only public input: their naming and granularity decide how debuggable the whole state layer is for a team.
## What an NgRx action is An **action** in NgRx is a plain JavaScript object with one required property, `type`, a string that names an event. Anything else on the object is **payload**: extra data the rest of the store needs to handle the event. In a bookstore catalogue, `{ type: '[Catalogue Page] Genre Changed', genre: 'poetry' }` says that the catalogue page reported a new genre choice. Actions are how a component asks the global `Store` for a state change. A component never writes state directly; it reports what happened and lets the store's other parts react. The `type` string follows NgRx's `[Source] Event` convention: the bracketed part names **where** the action came from (a page, an API, a browser API), and the rest names **what happened**. ## Declaring an action creator with `createAction` Nobody writes the object literal by hand. `createAction` from `@ngrx/store` returns an **action creator**: a function that builds the object with the right `type` every time, and gives TypeScript the payload's shape. `createAction` has three forms: | Form | Example | Call site | |---|---|---| | no payload | `createAction('[Catalogue Page] Opened')` | `cataloguePageOpened()` | | typed props | `createAction('[Catalogue Page] Genre Changed', props<{ genre: string }>())` | `genreChanged({ genre: 'poetry' })` | | creator function | `createAction('[Catalogue Page] Wishlist Toggled', (bookId: string) => ({ bookId }))` | `wishlistToggled('b-42')` | The creator also carries its type as a read-only `type` property, so `genreChanged.type` is the string `'[Catalogue Page] Genre Changed'`. Reducers and effects use the creator itself to say which action they handle. The `props<T>()` helper has guard rails in its typings. NgRx rejects props that are: - an **array** (`props<string[]>()`), - a **primitive** (`props<string>()`), - an **empty object** (`props<{}>()`), - an object with its own **`type`** property, which would clash with the action's type. ## Dispatching from a component A component injects `Store` and calls `dispatch` with the object the creator returns: ```ts import { Component, OnInit, inject } from '@angular/core'; import { Store, createAction, props } from '@ngrx/store'; export const cataloguePageOpened = createAction('[Catalogue Page] Opened'); export const genreChanged = createAction( '[Catalogue Page] Genre Changed', props<{ genre: string }>() ); @Component({ selector: 'app-catalogue', template: '' }) export class CatalogueComponent implements OnInit { private readonly store = inject(Store); ngOnInit(): void { this.store.dispatch(cataloguePageOpened()); } onGenreChange(genre: string): void { this.store.dispatch(genreChanged({ genre })); } } ``` `dispatch` with an action object returns `void`. The component does not get the new state back from the call; it reads state through selectors, and any follow-up work (loading the books for that genre) belongs to effects. ## What happens after `dispatch` 1. The action goes onto the store's actions stream. 2. Every registered reducer receives it and returns the next state for its slice. Most ignore it. 3. Effects that listen for its type run their side effects and may dispatch further actions. 4. Selectors built on the changed slices emit, and the views that read them update. Steps 2 to 4 are the subject of reducers, effects and selectors. The point here is that **one action goes to all of them**; the action does not name a handler. ## Mistakes that show up in interviews - **Passing the creator instead of calling it.** `store.dispatch(cataloguePageOpened)` does not compile: `dispatch`'s typings reject an action creator with the message "Action creator is not allowed to be dispatched. Did you forget to call it?". - **Treating an action as a method call.** `setBooks` or `loadBooksNow` reads like a command. NgRx's guidance is to capture events, such as `[Catalogue Page] Opened`, and let the store decide what they mean. - **Expecting a return value.** `dispatch` does not return a Promise or the new state, so `await store.dispatch(...)` waits for nothing. - **Hand-written object literals.** They skip the type-checked payload and invite typos in the `type` string. ## Choosing the source and the event text The bracketed source should name the **place** the event came from, not the feature it affects. For the bookstore: - `[Catalogue Page] Opened` and `[Catalogue Page] Genre Changed` come from the page component; - `[Books API] Books Loaded Success` comes from the code that talks to the server; - `[Wishlist Button] Book Added` comes from a reusable button that may sit on several pages. The event text is written in the past tense or as a plain description of what the user or system did. A reader scanning the DevTools log should be able to reconstruct the session from the types alone. With these rules in mind, an action is simply a typed, named report of an event, built by a creator and handed to `Store.dispatch`.
- What happens if you write store.dispatch(cataloguePageOpened) without calling the creator?It does not compile. `Store.dispatch` has a type check that rejects an action creator with the message "Action creator is not allowed to be dispatched. Did you forget to call it?". The check matters because a creator is a function, and `dispatch` also has an overload that accepts a function returning an action; without it, the slip would silently mean something else.
- Why does props<{ type: string }>() fail to compile?The creator spreads the props into the action object and then sets `type`, so a payload field called `type` would clash with the action's own type. NgRx's `props` typings reject it, along with arrays, primitives and empty objects. Rename the field, for example `bookType`.
saying these in an interview costs you the question
- An action names the reducer function the store should call.
- store.dispatch returns the new state, or a Promise you can await.
- You pass the action creator itself to dispatch, without calling it.
- Each action is delivered only to the one reducer that owns its slice.
- The type string is just a label, so a bare 'load' is as good as '[Catalogue Page] Opened'.