When it comes to managing state in Angular, NgRx stands out as the go-to library. We’re about to walk through a collection of battle-hardened conventions that will sharpen your workflow and boost your project's reliability.
Tomas Trajan
@tomastrajan
May 4, 2022
18 min read
Look to nature for inspiration! Be like nature 😉 (📷 & 🎨 by Tomas Trajan)
UPDATE 📻 Tune in to a super entertaining episode of the Angular Show Podcast ▶ ️on Spotify, featuring Brian Love and Nicole Oliver 🔥🔥🔥
UPDATE 🎥 Watch a recording that dives into the ideas from this post ▶ ️on the Angular Air Podcast, with Justin Schwartzenberger, Alyssa Nicoll, and Mike Brocchi 🔥🔥🔥
UPDATE 🎥 There's also another recording that delves deeper into the nuances of the topics covered here, hosted by Christian Lüdemann on his podcast!
Hello everyone! 👋
You might have noticed that my posting schedule slowed down a bit, but rest assured, there was a good explanation behind it… Especially considering the bright side of this situation!
Over the past couple of years, I've devoted my spare time to building OMNIBOARD, a top-tier tool for software engineers to comprehend and advance the polyrepo setups within their enterprise organizations. It offers a fairly robust free tier with unlimited projects, so be sure to give it a look! 😉
[OMNIBOARD](https://omniboard.dev/) gives you the ability to examine every repository in your project suite with tailored checks and craft interactive dashboards that help you grasp your setup, measure progress, and evolve your codebases
It’s probably not a surprise that Angular and NgRx are my go-to technologies — the exact stack I relied on when building Omniboard!
Shipping a SaaS product on your own pushes you to learn fast and forces a level of honesty about what works versus what doesn’t
Now I’m ready to pass those lessons on to you!
Chances are you’ve picked up a bunch of these practices already, either through hands-on NgRx work or from excellent community articles — but I can guarantee at least a couple will surprise you, and every single tip here comes with a real, working example!
⚠️ ☕☕☕☕☕ Brace yourself for a long read (~18 min); no need to finish it in one sitting — just return to the section you need when you’re working on it.
TL;DR
Employ schematics for generating complete NgRx state features in a single shot
Understand and take advantage of the NgRx 80/20 Rule
When you’re uncertain, lift your logic up a level (keeping a clean one-directional dependency structure)
Craft a dedicated view selector
view$(orviewModel$) for each of your container componentsReach for
RouterStoreand its selectors whenever you need router stateAlways frame (and build) your actions as “events”, never as “commands”
Embrace local selectors and actions that are scoped to a particular container (or interceptor, feature, etc.) — this makes adopting the rest of these pointers much easier
Let Effects handle coordination only; move the real work into services so effects remain tidy
Any RxJs stream can kick off an Effect
Get friendly with Redux DevTools and nip potential performance pitfalls in the bud as your state expands
NgRx General Tips
Use schematics to generate whole NgRx state features in one go
When it comes to building frontend apps with Angular, schematics are a standout feature!
With Angular Schematics, spinning up a new workspace, an application, the architecture, or any component, pipe, or service becomes a breeze — no more struggling with Angular’s explicit, albeit sometimes verbose, way of doing things.
Take this: typing ng g m features/some-feature --route some-feature --module app into your terminal sets up an entirely new lazy-loaded feature, wires it into the routing configuration, and even ships a container component. Yes, that’s all it takes!
NgRx brings its own schematics, the @ngrx/schematics package, so you can scaffold an entire NgRx state feature with just one command.
Say you need to introduce a new product page into your app. Two commands get you everything:
ng g m features/project --route project --module app
ng g @ngrx/schematics:feature features/project/state/project -a -c --module features/project/project
Example of files generated by running Angular Schematics to generate lazy loaded module and a NgRx state feature for that module
Beyond the obvious perk of skipping the manual file copying (or authoring), there’s another win here—consistency!
Uniform naming, coding conventions, patterns, and overall methodology let us hit the ground running across multiple features (or apps), and this is one of the standout advantages of building with Angular and NgRx!
🤔 Consideration: At times, a single feature may handle state for several entities, requiring more than one state module. In such cases, you can tweak the aforementioned schematics to produce a
state-<entity-name>/folder instead of the genericstate/directory.This approach keeps our feature tidy and intuitive, while also making it a breeze to relocate our state slice—for instance, elevating it to the
core/level so multiple lazy-loaded features can share it, a point we’ll cover in a dedicated part of this article!
Recognize and leverage the NgRx 80/20 Rule
Given its 2 million-plus monthly downloads, it’s fair to say NgRx enjoys widespread adoption!
That vast user base makes it all the more curious that the NgRx 80/20 rule has never surfaced in any online discussion I’ve seen!
The BRILIANT core of NgRx is that roughly 80% of the logic you write boils down to plain TypeScript functions, completely decoupled from Angular or RxJs
It’s true—the bulk of any NgRx implementation is nothing more than pure functions *, which are the most straightforward and digestible primitives found in any programming language!
NgRx architecture diagram showing that only NgRx Effects which are Angular services themselves are aware of Angular (dependency injection) and RxJs Observable streams
What else can we extract from this diagram? The elements that exist purely as data structures or as pure functions* are:
store merely an interface combined with a data structure holding the initial state
reducers implemented as pure functions
selectors take the form of pure functions*
actions expressed as data structures or pure functions*
* action creators and selectors are not guaranteed to be impure, for instance when a library like uuid is used to generate the payload, however the purity claim remains valid for nearly all real-world scenarios 😉
- effects dependency-injected Angular service, streams from RxJs
Now for the final 20%: NgRx effects are unique in that they represent the only section of the application with full awareness of Angular. Because these effects are themselves Angular services, they are able to inject whatever dependencies they need—like the store, HTTP client, or entirely custom business logic services—while their operations are carried out with RxJs Observable streams.
From what I have seen, RxJs tends to be the most difficult part of Angular application logic to grasp, so it is worth underlining that NgRx Effects provide a somewhat simplified, more controlled version of RxJs streams:
there is no need to manage subscription lifecycle since NgRx takes over subscriptions for the effects the moment they are initialized
the streams are separated into smaller units that are much simpler to understand, debug, and extend over time
individual effects are straightforward to combine — for instance, effect A can set off B1 and B2, and only after both complete can C be triggered (more details are covered later in the section devoted to orchestrating effects)
Why the 80/20 Rule Pays Off in NgRx
As outlined previously, about 80% of the code in an app built with NgRx revolves around plain data structures and pure functions — that is excellent news, because it keeps both the implementation and, more importantly, the testing of such code a very uncomplicated affair.
To illustrate, look at the examples below. Even if they seem unrelated at first glance, a function such as multiply is essentially no different in nature from a reducer or a selector…
Illustration of a straightforward pure function named multiply — it takes two inputs, produces one output, and is guaranteed to yield the identical output whenever those same inputs are supplied.
Don’t mind the size of this selector below—its inner workings aren’t important right now. What matters is that, for all its bulk, it operates on the same principle as the earlier multiply example from a conceptual standpoint (although you could still split it into smaller chunks to make testing and reuse even more straightforward…).
Example of a large NgRx selector, an easy to test pure function
Even when this kind of selector carries a substantial amount of logic, its testing remains straightforward…
At its heart, the NgRx selector test is nothing more than an invocation of a plain JavaScript function.
The identical methodology holds for NgRx reducers and custom action creators, leading us to the ultimate NgRx 80/20 rule…
NgRx shines because it lets us push the majority of the logic (80%) into simple pure functions that are straightforward to comprehend and verify!
Even though NgRx is fundamentally built on RxJs streams—used in template selectors or NgRx effects—it smartly hides these streams, sparing us from wrestling with their inherent complexity and potential pitfalls; this design choice is its brilliance. A heartfelt shout-out to the @ngrx crew!
Single Direction Dependency Chain -When unsure, elevate your logic a level
The ideal scenario for our Angular app is a clear, scalable structure akin to the illustration below…
Example of Angular application architecture with **eager core** and **lazy features** (which import **shared module** which provides simple reusable components, directives and pipes)
Should this not ring a bell, I strongly suggest a quick side trip to one of my earlier pieces, where I unpack why this layout pays off and walk through its implementation 😉
How to architect epic Angular app in less than 10 minutes! ⏱️😅
Now that we are on the same page about the broader architecture, we can zoom in on what it means for NgRx state features—those slices of state backed by their own modules.
♻️ A quick recap: When we talk about an NgRx state feature (module / state slice), we are referring to the output of the
@ngrx/schematicstooling, in particular thefeatureschematic, which cranks out the entire state slice in one go.
Example of a NgRx state feature generated by the NgRx **feature** schematics which includes whole NgRx setup
In everyday development, we frequently kick off a lazy feature with just a single dedicated state slice.
Once the requirements solidify or grow, we often need to share a specific NgRx state slice between two distinct lazy features within the same Angular application…
⚠️ Sharing imports across sibling lazy features is strictly off-limits—doing so would undermine the entire architecture and could even result in runtime failures, depending on the sequence of the user’s navigation across these features!
Given these constraints, we must satisfy the requirement using one of the following two approaches:
relocate the entire state feature to
core/so any lazy feature can import it while keeping the dependency graph clean and unidirectionaldivide the lazy feature’s state into two parts —with the reusable slice moved into a brand new state implementation in
core/and the feature-specific portion remaining within the original feature
Example of extracting part of the lazy feature state into core to make it available in whole application
This application design follows a fractal pattern, meaning it can theoretically expand indefinitely 😅
What that boils down to in practice is that our lazy features can nest lazy SUB-features, and those can nest lazy SUB-SUB-features, and so on.
These nesting levels follow the same principle: if a state slice from SUB-feature-A is needed by SUB-feature-B, the slice must be lifted to the parent lazy feature level—never straight to core/, just a single level higher.
Example of a fractal architecture - fingers crossed you never have to build that many lazy-loaded modules! 😅🤣
Follow me on Twitter so you never miss new Angular articles or rad frontend content!😉
NgRx Selectors
Selectors act as pure functions that fetch specific slices of the store’s state. They’re also the go-to spot for shaping derived state. Essentially, they’re nothing more than pure functions—totally independent of Angular or RxJs streams—so writing, testing, and grasping them comes naturally!
Craft spotless view$ (view model) selectors for your container components
NgRx truly shines here: it lets us trim down the logic in our components, which happen to be the trickiest Angular pieces to test—simply fantastic!
So, how exactly does NgRx lend us a hand?
We can boil down our component implementation to just two main concerns
pull and render state from the store by leveraging NgRx selectors
fire off actions in response to user actions (okay, that did rhyme nicely)
Even though this pattern is pretty brilliant—especially when you compare it to the logic-heavy Angular components we’re used to—plenty of developers still end up piling more and more selectors right into the component…
Example of Angular component which uses NgRx selectors and store to dispatch actions. Unfortunately, component still retrieves state from many selectors which leads to more complicated template and often multiple subscriptions to the same streams!
As the app evolves, it's common to accumulate numerous selectors across different parts of the state tree. Each of these separate selectors demands its own subscription in the template via the | async pipe, and frequently the same selector gets subscribed multiple times within one template.
Let's fix this common pattern by implementing a specialized view$ selector tailored to our container component.
Thanks to the dedicated “view$” selector, the component gets exactly the structured view state it needs, perfectly shaping the data to match the template's requirements, so rendering becomes straightforward!
Example of Angular container component which uses dedicated “view” NgRx selector which delivers view state with “perfect” shape for the component template to be rendered
Employing a dedicated view$ selector typically goes alongside wrapping the entire component template in <ng-container *ngIf="view$ | async as v">.
After subscribing to view$ and extracting its value into the local template variable v, you can directly access any state properties like v.tasks or v.loading.
Note that using *ngIf doesn’t cause any issue regarding missing content at first (e.g., before data is fetched). The | async pipe resolves the selector subscription immediately, and the selector always returns an initial value—at minimum, the store’s initial state.
This practice aligns perfectly with defining explicit loading and error states in store slices to provide appropriate user feedback.
🤔 Consideration: Some developers choose to name the view selector reference
viewModel$with a correspondingvmvariable. The naming convention isn’t critical as long as the concept of a dedicated “view” selector for container components is implemented 👍
An additional advantage is that testing becomes more straightforward because you can simply mock what this selector provides for the component view, which becomes decoupled from the outputs of other selectors.
Always use RouterStore and its selectors to access routing state
NgRx ships with a helpful small package named @ngrx/router-store …
Bindings to connect the Angular Router with Store. During each router navigation cycle, multiple actions are dispatched that allow you to listen for changes in the router’s state. You can then select data from the state of the router to provide additional information to your application — Official NgRx Docs
As indicated in the citation above, @ngrx/router-store enables us to retrieve Angular Router state via selectors—a highly convenient and preferable alternative to the standard approach. Let’s look at an example…
Example of a ad hoc “bridge logic” between Angular Router (especially ActivatedRoute API) and NgRx
Without @ngrx/router-store, obtaining the selected dashboard ID from the URL typically means injecting ActivatedRoute, pulling the ID, and then dispatching an action while keeping an eye on subscription lifecycles. That’s far from ideal.
Consider instead a scenario where you could simply…
Logic-less components are a healthy sign in any NgRx project — remember, UI components are the hardest and most time-consuming pieces to test!
Then pull the required dashboardId out of the URL (path param) via a selector inside a well-scoped, purpose-built effect…
Example of NgRx selector and effect implementation to retrieve dashboard Id from URL (path param) and use it to select dashboard in the application state by dashboard ID
What truly matters here is the selectRouteParam selector factory that ships with @ngrx/router-store — go ahead and review every selector and selector factory that is included by default!
List of all selectors and selector factories available out of the box when using @ngrx/router-store
NgRx Actions
Actions serve as a fundamental piece of the NgRx architecture. An action represents a distinct occurrence taking place across your app. This includes interactions users initiate, external calls triggered over the network, or direct access to device APIs, all of these scenarios and beyond are captured through actions NgRx Docs
Name your Actions as “Events” instead of “Commands”
Take the following effect, which is meant to write the userId into the URL's query parameters.
NgRx effect which reflects `userId` into the URL as a query parameter
The effect itself is perfectly fine and performs exactly as expected… Still, there’s definitely some room for refinement!
Consider this: a sizable application could host several unrelated features, each requiring the ability to inject a test user into the URL query params.
Under such circumstances, merely reviewing the sequence of dispatched actions in the “application history”—i.e., the Redux Dev Tools (browser extension) action log—won’t give us a clear picture of what's happening.
So, let’s enhance our solution by renaming both the action and the effect accordingly!
NgRx effect which reflects **userId** into the URL as a query parameter with better naming and multiple trigger actions to improve readability of the “application history”
The effect itself was renamed from
changeTestUsertoreflectTestUserIdIntoQueryParams, offering far greater clarityBased on the **action origin**, we divided the action into two (or potentially more) distinct actions
Instead of the “command” pattern (like
changeTestUser), we adopted the “event” pattern for naming actions, which answers the question of what occurred, such astestUserSelected
Wonderful—our effect now flows like a well-formed sentence!
Reflect test user ID into query params (in the event that) test user was chosen (via the toolbar) or (in the event that) test user was chosen (within a particular business process)
With the understanding that effects describe events happening in the app, the second point becomes much clearer.
Action naming should emphasize their source (origin), rather than their target (reducer or effect)!
Let’s examine the changeTestUser action, which operates as a **command**, signaling a “destination” where specific logic—whether a reducer, an effect, or both—will handle it.
Because such an action can be dispatched from various parts of the application, without appropriate naming, we’d have no clue what transpired when inspecting the Redux DevTools action history…
However, by correctly labeling it as an **event**, such as testUserSelected, we can generate multiple actions sharing the same event name but with differing origins:
[Toolbar] Test User Selected[Test User Selector Widget] Test User Selected[Some Business Flow] Run As Test User Selected
By naming actions as events, we gain far richer insight into our application’s inner workings!
Embrace local selectors and actions specific to given container, widget or interceptor…
In reality, not every selector or actions file belongs to a generated state (feature) module—these are eager or lazy state features that would result from executing the ng g @ngrx/schematics:feature schematics.
When that’s the case, it’s perfectly acceptable to establish dedicated selectors or actions files, situated alongside its consumer. Take the auth-interceptor as an example; to pull state from multiple state slices, we’ll implement a dedicated selector file here.
Example of a dedicated local NgRx selector that supplies state to the auth-interceptor by merging data from selectors exposed by other eager NgRx state modules (slices / features)
Example of a NgRx local selector implementation which combines data exposed by the selectors of other eager state slices
NgRx Effects
With NgRx, side-effects can be modeled as a **distinct, well-supported concept** that has native library backing.
This built-in side-effects handling is what differentiates NgRx from other Angular state management solutions, many of which only offer partial or no such support…
Effects are for Orchestration
NgRx effects serve as the designated home for all asynchronous workflows, transformations, and general side-effectful logic…
It’s genuinely beneficial that NgRx introduces a formal concept for this kind of code, enabling uniform, clean implementations across diverse features and entire applications.
⚠️ When writing NgRx effects, you might be tempted to bypass a dedicated feature service and put all logic—requests, data mapping, everything—straight into the effect itself. Resist that urge!
Keep NgRx effects as lean as possible, and delegate the core business logic to dedicated services
Furthermore, while you could technically handle complex asynchronous orchestration in a single effect—resulting in a massive RxJS stream—it is far wiser to break it down into several smaller effects.
Each successful effect then emits its success action, triggering the next in the chain. This design also supports spawning multiple concurrent processes and later aggregating their results for subsequent sequential steps.
This is a sample of NgRx effect code that could have been combined into one effect, yet separating it into several focused standalone effects makes it far more maintainable and clearer to follow
Effects have the ability to be initiated by any RxJs observable stream
The majority of our NgRx effects are driven by the actions$ stream. This is highlighted by the way that when we scaffold an NgRx state feature via @ngrx/schematics, the resulting effect template includes a snippet that is identical in nature to that example implementation.
Example of a NgRx effect generated as a part of NgRx **feature** generated by **@ngrx/schematics**, the effect is triggered by the stream of all **actions$** out of the box
Effects don't have to rely exclusively on actions — they can be driven by virtually any RxJs-based Observable source. This flexibility unlocks a range of typical scenarios, such as:
scheduling recurring tasks (e.g., periodic refresh of an authentication token, batch upload of logs to the server, or notifying users of an upcoming session timeout before it happens, …)
responding to real-time user behavior (e.g., listening to scroll position changes to lazily fetch additional content when the user reaches the page bottom, …)
An NgRx Effect sample that fires when the user reaches the bottom of the page
- Using an NgRx selector as the trigger for an effect in response to store state updates (e.g., toggling a notification popup depending on the notification count in the store, taking action when query parameters shift)
Example of a NgRx Effect triggered by the NgRx selector stream instead of more common stream of **actions$**
Get comfortable using Redux DevTools
A particularly valuable habit from the earlier list is the *naming of Actions as “Events” rather than “Commands”. Once this convention is in place, a remarkable by-product arises (not in the NgRx sense 😉): the application history turns into a far more effective diagnostic resource for tracing issues and pinpointing the source of faulty behavior.*
Be sure to install and get acquainted with the Redux DevTools!
These DevTools were initially crafted to deliver a clear view of the action history and state snapshot for applications relying on the Redux state management library.
NgRx mirrors those patterns, only in a manner that integrates seamlessly with Angular as the host framework. This makes them an obvious choice.
The connection between NgRx and Redux DevTools is wired through the @ngrx/store-devtools package. Adding it, at minimum, during development is strongly recommended.
As the application and its managed state expand, a fresh challenge can surface…
Instrumentation for the Redux DevTools must serialize the entire application state for each dispatched action.
It’s easy to see how this becomes unwieldy when the state is substantial and actions fire at a rapid pace. The serialization process slows down (or might even freeze), while memory usage skyrockets!
This is certainly NOT the desired outcome, but a remedy exists. The @ngrx/store-devtools enables the specification of custom functions for the actionSanitizer and the stateSanitizer.
Provide custom action and state saniziter for NgRx StoreDevtoolsModule
From my real-world experience, stripping the entire payload from
<LoadSomeData>Successactions proves highly beneficial; that same data is already accessible under the network tab in browser dev tools, so duplicating it within actions adds no value for debugging.
An NgRx actions sanitizer example that removes all payloads from `When it comes to the state sanitizer, the approach varies by application, making universal advice difficult. A possible state sanitizer illustration might involve cutting down a lengthy array of sizable elements to, for instance, merely 5 entries…
Example of NgRx store state with truncated list of items to prevent performance issue, it's a custom implementation, so it will be specific to given application and application state
As of 2022, the Angular + NgRx is the TOP-TIER CHOICE* for achieving high efficiency, helping you build complex, resilient front-end apps with a well-structured design that comfortably handles dozens of features — whether you’re a lone developer or part of a large enterprise with numerous teams and contributors!
* assuming a solid grasp of both technologies
That wraps it up for now! 🔥
I trust you enjoyed discovering these production-tested NgRx tips and the advantages you can gain from applying them in your own work!
Feel free to reach out with any questions via the article comments or Twitter DMs @tomastrajan — I’m happy to help.
And always remember, the future looks bright
Clearly, that’s the promising road ahead! (📷 by André Filipe)
Are you into the look of the code snippet? Check out our fresh theme plugin
Skol - the best IDE theme out there
Bring the aurora borealis straight into your editor. A clean, punchy dark theme that’s easy on the eyes and looks fantastic.
Create smarter interfaces with Angular and AI
Video Course: Angular and AI
This practical workshop walks you through embedding AI capabilities in Angular applications, using Hash Brown to craft intelligent, responsive interfaces.
You'll master real-time chat, function invocation, dynamic UI creation, and structured data output, all through guided exercises.
If you find value in these insights and believe your team or company could use additional expert guidance—let’s talk.
NgRx for Angular: Structured State Management Workshop
Are you tired of patching together fragile state management in your Angular app with one-off services?
Take full control of your application state by adopting NgRx’s proven, scalable patterns and best practices!
Enjoying this article and eager to delve into Angular’s brand new Signal Forms?
Angular Signal Forms: Hands-On Masterclass
Angular's newly introduced Signal-Forms are unpacked across 12 step-by-step sections that blend conceptual grounding with practical exercises.
The curriculum covers core form mechanics, validation rules, bespoke controls, nested form structures, and pathways for migrating existing setups, among other topics.
Stay in the loop
for future posts
Subscribe to Angular Experts Content Updates & News, and we will let you know the moment a fresh blog post on Angular, Ngrx, RxJs, or other compelling Frontend subjects goes live!
Your email address stays confidential, and you hold the power to opt out whenever you like!
Join the discussion
Feel free to raise questions, contribute your insights, or offer your take on the subject matter
Tomas Trajan
Google Developer Expert (GDE)
for Angular & Web Technologies
My focus is on helping development teams succeed with Angular, providing both training and consulting with a strong emphasis on architecture and state management using NgRx!
As a Google Developer Expert for Angular and Web Technologies, I work as a consultant and Angular trainer. Currently, I support teams in enterprise organizations worldwide by building core features and architectures, promoting Best Practices, transferring knowledge, and refining their workflows.
Tomas is committed to consistently delivering high value to both clients and the broader developer community. This is reflected in his substantial record of widely-read industry articles, presentations at international events and meetups, and his involvement with open-source projects.
52
Blog posts
4.7M
Blog views
3.5K
Github stars
612
Trained developers
39
Given talks
8
Capacity to eat another cake
You might also like
Browse these additional Angular Experts blog posts to go deeper into subjects such as NgRx or Angular !

Top 10 Angular Architecture Mistakes You Really Want To Avoid
In 2024, Angular keeps changing for better with ever increasing pace, but the big picture remains the same which makes architecture know-how timeless and well worth your time!

Tomas Trajan
@tomastrajan
Sep 10, 2024
15 min read

Angular Signal Inputs
Revolutionize Your Angular Components with the brand new Reactive Signal Inputs.

Kevin Kreuzer
@nivekcode
Jan 24, 2024
6 min read

Improving DX with new Angular @Input Value Transform
Embrace the Future: Moving Beyond Getters and Setters! Learn how to leverage the power of custom transformers or the build in booleanAttribute and numberAttribute transformers.

Kevin Kreuzer
@nivekcode
Nov 18, 2023
3 min read
Leverage our proven expertise for your entire team
Through countless engagements with both large corporations and emerging startups, we've delivered workshops, crafted tutorials, and sustained valuable open-source projects. It's a point of pride for us to harness our deep insights into contemporary front-end development, and we'd be delighted to drive your organization's growth
