skip to content

Store Setup & Actions

Actions are the only way into the store: typed creators, grouped by source and dispatched as events, not commands. Interviewers ask why that hygiene keeps the DevTools log readable.

on this pageshow

explore

questions

6

In NgRx, what is an action, and how does a component define one with createAction and send it to the Store?

level: juniorimportance: must knowfreq 66%

answer

  1. a plain object with a type
  2. describes something that happened
  3. [Source] Event in the type string
  4. createAction plus props<T>()
  5. call the creator, then dispatch

basics

~20 s

An 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 s

An 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 lines
ts
import { 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

for a junior

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.

for a middle

Explain the three createAction forms, why the creator carries a read-only type property, and why dispatch returns nothing: state comes back through selectors.

for a senior

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.

for a principal

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'.
open as a page

In NgRx, what does createActionGroup generate from a source and an events map, and why does a payload-less event need emptyProps()?

level: middleimportance: must knowfreq 55%

basics

~20 s

createActionGroup returns one action creator per event, named by camel-casing the event name, with type '[Source] Event Name'. A payload-less event needs emptyProps() because the events map needs a value and props<{}>() fails NgRx's empty-object type check.

open as a page

In NgRx 22, how does provideStore() differ from StoreModule.forRoot(), and where must a standalone app register the root store?

level: middleimportance: should knowfreq 42%

basics

~20 s

provideStore() and StoreModule.forRoot() configure the same root store with the same arguments. provideStore() returns EnvironmentProviders for a standalone app's application config; forRoot() goes in an NgModule's imports. Register the root once; a second registration throws.

open as a page

An NgRx bookstore app dispatches one shared loadBooks action from the catalogue page, the genre filter and a refresh button; why is that poor action hygiene, and how would you restructure it?

level: seniorimportance: should knowfreq 46%

basics

~20 s

A shared loadBooks command hides which source fired it, so the DevTools log shows identical entries and handlers cannot treat sources differently. Dispatch one [Source] Event action per source instead, and let the loading reaction listen for all of them.

open as a page

In NgRx, what do provideStoreDevtools' maxAge and logOnly defaults mean for a production build, and how would you configure the DevTools there?

level: seniorimportance: should knowfreq 33%

basics

~20 s

provideStoreDevtools defaults to maxAge: false, an unbounded action and state history, and logOnly: false, full time travel and dispatch from the extension. In production, cap maxAge and set logOnly from isDevMode(), or leave the DevTools out of the bundle.

open as a page

In NgRx 22, what does store.dispatch(() => genreChanged({ genre: this.genre() })) do, and how is that reactive dispatch cleaned up?

level: seniorimportance: nice to knowfreq 24%

basics

~20 s

Passing a function to Store.dispatch creates an Angular effect that dispatches the returned action initially and again whenever a signal read inside it changes. It returns an EffectRef, and ends with its injector: the caller's, a passed one, or the root.

open as a page