Level Up Your NgRx Skills With 10 Time-Tested Best Practices
NgRx is the most popular state management library for Angular, let's explore set of real world proven best practices to level up your skills and make your projects a success!
Tomas Trajan
@tomastrajan
May 4, 2022
18 min read
Nature knows the best! Be like nature 😉 (📷 & 🎨 by Tomas Trajan)
UPDATE 📻 Check out VERY FUN episode of Angular Show Podcast at ▶ ️Spotify with Brian Love, Nicole Oliver 🔥🔥🔥
UPDATE 🎥 Check out recording discussing concepts from this article at ▶ ️Angular Air Podcast with Justin Schwartzenberger, Alyssa Nicoll & Mike Brocchi 🔥🔥🔥
UPDATE 🎥 Check out another recording focusing on more in-depth aspects of the concepts explored in this blog post with Christian Lüdemann at his podcast!
Hey folks! 👋
Some of you might have realized my article publishing frequency went a bit stale for quite some time but you can be sure it was for a good reason… Especially because there is a silver lining to all of this!
Last two years, I have spent all my free time developing OMNIBOARD the best tool for software engineers that helps them to understand and evolve polyrepo environments of their enterprise organization, it has a pretty powerful free plan with unlimited projects so make sure to check that out! 😉
[OMNIBOARD](https://omniboard.dev/) allows you to analyze all your project repositories with custom checks and create dynamic dashboards to understand your environment, track progress and evolve code basesAs you might have guessed, my preferred tools to work with are Angular & NgRx and that’s exactly what I used to build Omniboard!
Developing a SaaS product as a solo developer allowed me to gain valuable experience while being forced to be brutally honest about what does and doesn’t work!
And that's exactly what I am going to share with you now!
While you might have figured out many of the best practices by yourself when using NgRx in one of your projects or by reading about them as there are many great NgRx articles out there, You can bet there is always that one or two things which are going to be new and worth learning, plus there will be realistic code examples for every tip!
⚠️ ☕☕☕☕☕ This is a pretty long article (~18min) so don’t worry about reading the whole thing in one go, just come back when you need to refresh specific part you’re working on.
TL;DR
Use schematics to generate whole NgRx state features
Recognize and leverage the NgRx 80/20 Rule
When in doubt, pull your logic one level up (to preserve nice clean one way dependency graph)
Create perfect view selector
view$
( orviewModel$
) for your container componentsAlways use
RouterStore
and its selectors to access router stateAlways name (and implement) your actions as “events” instead of “commands”
Embrace local selectors and actions specific to given container ( or interceptor, feature, … ) which makes implementing of other best practices a breeze
Effects are for orchestration only, make sure that the actual logic is in services to keep the effects clean
Effects can be triggered by ANY RxJs stream
Get comfortable using Redux DevTools and prevent potential performance issues as your application state grows
NgRx General Tips
Use schematics to generate whole NgRx state features in one go
Schematics are one of the best things about using Angular to create our frontend applications!
Angular Schematics allow us to generate new workspace, application, architecture and every component, pipe or service with ease and hence not feeling any pain with regards to Angular’s explicit (some would say verbose) approach to building applications.
For example, running ng g m features/some-feature --route some-feature --module app
in our terminal will create a whole new lazy loaded feature for our application which will be integrated in to the route configuration and comes with a container component out of the box. Yes, it can be that easy!
NgRx comes with its own schematics package called @ngrx/schematics
which allows us to setup whole NgRx state features by running a single command.
Let's imagine we want to add new product feature (page) to our application. We can get a whole setup just by running two commands:
ng g m features/project --route project --module app
ng g @ngrx/schematics:feature features/project/state/project -a -c --module features/project/project
Besides obvious advantage like not spending time copying (or writing) the files by ourselves there is an additional benefit of consistency!
Consistency in naming, code style, patterns and general approach allows us to be effective when working on multiple features (or applications) from the get go and is one of the best things about Angular and NgRx based development!
🤔 Consideration: Sometimes, we might need more than one state module per feature as feature manages state of multiple entities. In that case the above-mentioned schematics can be adjusted to generate
state-<entity-name>/
folder instead of plainstate/
folder.That way our feature will stay clean and easy to understand, plus it will become easy to move our state slice around, for example in case we need to pull it one level up to the
core/
so that it can be reused by more than one lazy feature of our application as will be discussed in dedicated section of this article!
Recognize and leverage the NgRx 80/20 Rule
With more than 2 million downloads per month, it should be pretty safe to state that NgRx is used by lots of folks out there!
The amount of users makes it that much more surprising that I have never seen it discussed online, the NgRx 80/20 rule!
The MOST GENIUS things about NgRx is that whole 80% of logic we have to write is just plain TypeScript functions which are NOT aware of Angular or RxJs
That’s right, the majority of NgRx implementation is as simple as it gets just pure functions * which are the simplest and easiest to understand building blocks of every programming language!
NgRx architecture diagram showing that only NgRx Effects which are Angular services themselves are aware of Angular (dependency injection) and RxJs Observable streamsLet’s dissect this diagram further. The concepts implemented as data structure or pure functions* are:
store just an interface and initial state data structure
reducers pure functions
selectors pure functions*
actions data structures/pure functions*
* action creators and selectors could also be impure, for example when using something like uuid to generate the payload but the purity claim will hold for most practical purposes 😉
- effects Angular service (dependency injection), RxJs streams
On the other hand, last 20%, the NgRx effects are the only place aware of Angular as the NgRx effects themselves are implemented as Angular services so they can inject other services (store, http client, custom business logic services or anything else really…) and also implement logic with help of RxJs Observable streams.
In my experience, RxJs usually represents the most challenging part of the Angular application logic so it is important to point out that the NgRx Effects come with their own toned down / tamed version of RxJs streams:
we do NOT have to manage subscription lifecycle as NgRx effects are subscribed by the NgRx when the effect is initialized
streams are broken down into small chunks which are easier to understand, fix and evolve
small effects can be composed effectively, eg effect A triggers B1 and B2, once both of them are done, the C is triggered (more on that in the dedicated effects orchestration part of this article)
The Benefits of NgRx 80/20 Rule
As we have discussed, up to 80% of NgRx based application code is implemented using plain data structures and pure functions which is great news as it makes the implementation and especially testing a very straight forward business!
Let’s consider following example s even though they might not seem the same on the surface there is fundamentally no difference between something like function multiply
and a reducer or a selector…
Excuse the following huge selector, you can ignore its implementation, the point is even though is large, it’s basically the same as the previous multiply
function on the philosophical level (even though it could probably be further broken down in smaller parts for even easier testability and reusability…)
Such selector, even though contains a lot of implementation can be tested in a very simple fashion…
Example of the NgRx selector test, at its core its just a call to pure JavaScript functionThe same principles would apply to NgRx reducers and custom action creators which brings us to a final conclusion of NgRx 80/20 rule…
NgRx is great because it allows us to implement as much logic as possible (80%) in the form of simple pure functions which are easy to understand and test!
NgRx while purely RxJs stream based (selectors are consumed as RxJs streams in the component template or NgRx effects) cleverly abstracts those RxJs streams away so that we do not have to struggle to avoid all the pitfalls and complexity of dealing with such streams, and that’s the genius of its architecture, thank you @ngrx team!
One Way Dependency Graph -When in doubt, pull your logic one level up
Hopefully our Angular application is following simple scalable architecture like one in the following image…
Example of Angular application architecture with **eager core** and **lazy features** (which import **shared module** which provides simple reusable components, directives and pipes)If not, I highly recommend you to take a detour and check one of my previous articles which dives deep on advantages of such architecture and how to implement it 😉
How to architect epic Angular app in less than 10 minutes! ⏱️😅
As we have aggreged on the overall architecture, it allows us to take a closer look on its implications for NgRx state features (modules / state slices)
Example of a NgRx state feature generated by the NgRx **feature** schematics which includes whole NgRx setup♻️ A small recapitulation: In general, when we speak about NgRx state feature (module / state slice) we mean files generated by the
@ngrx/schematics
, especially thefeature
schematic which generates the whole state feature.
Now, when we develop application it is common to start with single state slice per lazy feature.
As the requirements become more clear or are expanded, it is often the case that we need to access NgRx state slice from one lazy feature in another lazy feature of our Angular application…
⚠️ Importing stuff between sibling lazy features is forbidden because it would break all the benefits of such architecture and could in theory lead also to runtime errors based on order in which user navigated to individual lazy features!
Because of these reasons, the only way to fulfill our requirement is to either:
extract whole state feature into
core/
which then can be imported by any lazy feature while preserving clean one way dependency graphsplit state feature of the lazy feature into two reusable state will be extracted in to fresh new state slice implemented in
core/
while the lazy feature specific state will stay in that feature
The described application architecture is fractal, which means it can grow infinitely at least in theory 😅
In practice this means our lazy features can have lazy SUB-features
which can have lazy SUB-SUB-features
, …
The same rules will apply in those cases, if we had a SUB-feature-A
state which we need to access in SUB-feature-B
then we have to pull that state slice one level up into parent lazy feature
(so not core/
, always just one level up)
Follow me on Twitter because you will get notified about new Angular blog posts and cool frontend stuff!😉
NgRx Selectors
Selectors are pure functions used for obtaining slices of store state. More so, selectors are perfect place to create all your derived state. In practice, they are just pure functions without any notion of Angular or RxJs streams so they are naturally easy to write, test and understand!
Create perfect view$ (view model) selector for your container components
One of the best properties of NgRx is that it allows us to minimize implementation of our components which are coincidentally also hardest part of Angular to test, just amazing!
How does NgRx supports us in this case?
Well, our components implementation can be reduced to two concepts
retrieve and display state from the store using NgRx selectors
dispatch actions based on user interactions (yes, that actually rhymes)
While this solution is really amazing, especially compared to our standard logic riddled Angular component, developers often choose an approach where they keep adding more and more selectors to the component itself…
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!Over time, we might end up with many selectors from various state slices. All those independent selectors must be subscribed one by one in the template using the | async
pipe. Even worse, we often end up with multiple subscriptions per selector in a single template!
Let’s improve the status quo by introducing dedicated view$
selector for our container component.
Example of Angular container component which uses dedicated “view” NgRx selector which delivers view state with “perfect” shape for the component template to be renderedThe component will use single dedicated “view$” selector which will deliver tailor made view state which has the perfect shape for the components template so it can be rendered with ease!
Using dedicated view$
selector comes usually hand in hand with wrapping of the whole components template with <ng-container *ngIf="view$ | async as v">
.
Once we subscribed to the view$
and unwrapped its value in the local template v
variable we can start using it to access any state properties such as v.tasks
or v.loading
!
Please consider that using *ngIf
doesn’t represent any problem in regard to not seeing any content in the beginning (for example, before we loaded data). Subscribing to selector with | async
pipe is resolved immediately and the selector will always provide initial value which is at least the initial state from the store.
This goes hand in hand with having explicit loading and error states in our store state slices to show relevant user feedback!
🤔 Consideration: Some developers prefer to store reference to the view selector in the
viewModel$
andvm
respectively. Remember, it doesn’t really matter how you call it as long as you implement the concept of dedicated “view” selector for our container components 👍
Another benefit of this approach that if further simplifies testing because we can just mock what this selector deliver for the component view which becomes decoupled from what other selectors are delivering.
Always use RouterStore
and its selectors to access routing state
NgRx comes with a nice little package called @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 highlighted in the quote above, the @ngrx/router-store
allows us to access Angular Router state using selectors which is extremely convenient and preferable compared to default way of doing things. Consider the following example…
We need to get selected dashboard ID from the URL and the standard way to do that without @ngrx/router-store
would be to inject ActivatedRoute
, then retrieve the ID and dispatch appropriate action while manually managing subscription state, there must be a better way!
Imagine a world where you can just…
Logic-less component, a common positive occurrence in NgRx codebases, don’t forget that the components are the hardest and slowest to test!And retrieve desired dashboardId
from URL (path param) using a selector in nice dedicated effect…
The key part here is the selectRouteParam
selector factory provided by the @ngrx/router-store
, check out the list of all selectors and selector factories available out of the box!
NgRx Actions
Actions are one of the main building blocks in NgRx. Actions express unique events that happen throughout your application. Everything from user interaction with the page, external interaction through network requests, and direct interaction with device APIs, these and more events are described with actions NgRx Docs
Name your Actions as “Events” instead of “Commands”
Let’s consider the following effect which should reflect userId
into the query params of the URL.
There is nothing inherently wrong with the effect itself as it will do its job just fine… Nevertheless, there is still a room for some improvements!
Imagine a situation where a large application might contain a couple of unrelated features which all need to be able to set test user into the query params of the URL.
In that case, we won't be able to really understand what is going on just by observing the list of actions as they happened in the “application history” or more precisely, the actions log of the Redux Dev Tools (browser extension)
Let’s improve our implementation by adjusting the name of the action and the effect itself!
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”We renamed the effect itself from
changeTestUser
toreflectTestUserIdIntoQueryParams
which is much more descriptiveWe split action into two (or potentially more) actions based on the action origin
We renamed actions to follow “event” pattern ( what happened?
testUserSelected
) instead of “command” pattern ( egchangeTestUser
)
Great, our effect now reads like a sentence!
Reflect test user ID into query params (when) test user was selected (in the toolbar) or (when) test user was selected (as a part of some business flow)
Now that we established that effect describe events which happened in our application, the second point will make much more sense.
The actions should be named in a way which focuses their source (origin) and NOT to their destination (reducer / effect)!
Consider the changeTestUser
action which is basically a command which implies it belongs to a “destination” where it will be performed by some logic (either reducer or effect or both).
Such action then might be dispatched from multiple parts of application, and we would have no way of knowing what happened just by viewing the application action history in the Redux DevTools…
Now if we name it correctly like an event, for example testUserSelected
, we might create many such actions with different origins:
[Toolbar] Test User Selected
[Test User Selector Widget] Test User Selected
[Some Business Flow] Run As Test User Selected
Naming actions as events gives us much better insight into what is going on in our application!
Embrace local selectors and actions specific to given container, widget or interceptor…
In practice, not every selector or actions file belongs to a generated state (feature) module (eager or lazy state feature which we would get when running ng g @ngrx/schematics:feature
schematics)
In those cases, it is more than alright to create a dedicated selectors or actions files which is co-located next to its consumer. Consider the following example of auth-interceptor
which needs to retrieve state from multiple state slices and because of that we are going to introduce a dedicated selector file.
NgRx Effects
NgRx allows us to implement side-effects as a well defined separate concept with first class support from the library itself!
First class side-effects support sets NgRx apart from the other Angular state management libraries which support it partially or not at all…
Effects are for Orchestration
NgRx effects are dedicated place to implement all the asynchronous flows, processing and any side effects in general…
It’s really great that NgRx prescribes dedicated concept for such type of logic which leads to consistent clean implementation across wide range of application features and applications themselves.
⚠️ While implementing NgRx effects, it might be temping to skip creation of a dedicated feature service and implement all the logic, requests, transformation and what ever have you directly in the effect itself do NOT do that!
NgRx effects should be as lightweight as possible and the real business logic implementation should be handled by dedicated services
More so, even though is possible to implement complex asynchronous orchestration as a single effect which means it’s gonna be a complex RxJs stream it is MUCH better to split it into multiple smaller effects.
Every successful effect is then going to trigger the next one with its success action. This allows also for branching out for multiple parallel processes and eventually collecting all the results for following serial processing steps.
Example of NgRx effect logic which could have been implemented as a single effect but is much more manageable and easier to understand when split into multiple smaller standalone effectsEffects can be triggered by ANY RxJs Observable stream
Most of our NgRx effects are triggered by the stream of actions$. This occurrence is underlined by the fact that when we generate NgRx state feature using @ngrx/schematics the generated effect contains exactly such snippet as an 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 boxThe thing about the effects is that they can be triggered by ANY RxJs Observable stream. This can help us solve many common use cases like:
triggering of some periodic processing with given interval (eg refreshing of an auth token, uploading of logs to backend, prompting user to take action before she will be logged out, …)
reacting to some user interaction (eg stream of scroll events to trigger loading of more data as the user scrolls to the bottom of the page, …)
- reacting to the store state changes using NgRx selector as an effect trigger (eg show / hide notification popup based on the amount of notifications in the store, perform something when query params change)
Get comfortable using Redux DevTools
The one of the most important best practices discussed previously is the *Name your Actions as “Events” instead of “Commands”. As we apply it to our codebase we will discover an amazing side-effect (not in NgRx sense 😉) that our application history will become much more useful tool for understanding what is going on and what went wrong in case we encounter some buggy behavior.*
Make sure that you install and familiarize yourself with the Redux DevTools!
The Redux DevTools were originally created to provide nice application history and state overview for the apps using Redux state management library.
NgRx implements the same patterns, just in a way that plays really nice with Angular as the host framework. That makes them a natural fit.
NgRx / Redux DevTools integration is implemented in the @ngrx/store-devtools
package. It’s definitely a good idea to add this at least in development mode.
As our application and especially managed state grows, we might encounter a new problem…
The state instrumentation for the Redux DevTools basically has to serialize whole application state, for every action that has happened.
As we can imagine, it will get out of hand pretty quickly if our state is large and our application dispatches actions with high frequency. The serialization itself becomes slow (or even grinds to halt) while the memory consumption will be sky high!
This is definitely NOT what we want but luckily there is a solution. The @ngrx/store-devtools
allows us to provide custom implementation for the actionSanitizer
and the stateSanitizer
.
Example of a NgRx actions sanitizer which will strip any payload from `In practice, I found it very useful to remove the whole payload from
<LoadSomeData>Success
actions as that state can be viewed in the network tab of the browser dev tools so it is not necessary to duplicate it in actions for debugging purposes.
As for the state sanitizer, it will depend on particular application so it’s hard to provide generally applicable guidelines. One example of state sanitizer could be truncating of a long list of large items to let’s say only 5 items…
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 stateAs of 2022, the Angular + NgRx represents THE BEST COMBO* for high productivity which enables you develop complex and robust frontend applications with clean architecture which can scale to support 10s of features with ease, from solo developers to whole enterprise organizations with many teams and developers!
* if we can assume general competence with both technologies
And that’s it for today! 🔥
I hope You enjoyed learning about real world proven NgRx best practices and what benefits You can reap by implementing these ideas in your projects!
Also, don’t hesitate to ping me if you have any questions using the article responses or Twitter DMs @tomastrajan
Obviously the bright future! (📷 by André Filipe)And never forget, future is bright
Do you enjoy the theme of the code preview? Explore our brand new theme plugin
Skol - the ultimate IDE theme
Northern lights feeling straight to your IDE. A simple but powerful dark theme that looks great and relaxes your eyes.
Prepare yourself for the future of Angular and become an Angular Signals expert today!
Angular Signals Mastercalss eBook
Discover why Angular Signals are essential, explore their versatile API, and unlock the secrets of their inner workings.
Elevate your development skills and prepare yourself for the future of Angular. Get ahead today!
Get notified
about new blog posts
Sign up for Angular Experts Content Updates & News and you'll get notified whenever we release a new blog posts about Angular, Ngrx, RxJs or other interesting Frontend topics!
We will never share your email with anyone else and you can unsubscribe at any time!
Responses & comments
Do not hesitate to ask questions and share your own experience and perspective with the topic
Tomas Trajan
Google Developer Expert (GDE)
for Angular & Web Technologies
I help developer teams deliver successful Angular applications through training and consulting with focus on Architecture and State managements with NgRx!
A Google Developer Expert for Angular & Web Technologies working as a consultant and Angular trainer. Currently empowering teams in enterprise organizations worldwide by implementing core functionality and architecture, introducing best practices, sharing know-how and optimizing workflows.
Tomas strives continually to provide maximum value for customers and the wider developer community alike. His work is underlined by a vast track record of publishing popular industry articles, leading talks at international conferences and meetups, and contributing to 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
Check out following blog posts from Angular Experts to learn even more about related topics like 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
@kreuzercode
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
@kreuzercode
Nov 18, 2023
3 min read
Empower your team with our extensive experience
Angular Experts have spent many years consulting with enterprises and startups alike, leading workshops and tutorials, and maintaining rich open source resources. We take great pride in our experience in modern front-end and would be thrilled to help your business boom