Angular Ngrx DevTools
What are the Ngrx DevTools?
The Ngrx DevTools are a Chrome / Firefox browser extension that includes a UI for inspecting and interacting with a Ngrx-based application.
As an example, here is a screenshot of the Ngrx DevTools in action:
The main features of the Ngrx DevTools
As we can see, inside the Ngrx DevTools we have:
• An Action Log, that gives us a great understanding of how the application works, and what parts of the application are triggering which Actions
• A State inspector, that allows us to easily inspect the in-memory store state
• A Time-travelling debugger (the Play button and timeline at the bottom), that allows us to replay any Action at any given point of the debugging session, and even replay the whole session while navigating through multiple screens
What are the benefits of the Ngrx DevTools?
Here are some of the benefits of the Ngrx DevTools:
• We can visually see the content of the store at any moment, which is essential for debugging
• We can have a new developer on the team inspect the application with the DevTools, and have it get a good initial understanding of how the application works
• If we manage to get the client state of users in production, we can use the DevTools to reproduce bugs locally, just by importing the user production state
Bugs locally, just by importing the user production state
The key benefit of the DevTools is that it gives us some immediate visual feedback about what the application is doing at all times, making it much easier to understand what is going on.
Installing the Ngrx DevTools with Ngrx Schematics
It’s best to setup the Ngrx DevTools from the very beginning of the project. We can setup an Ngrx Store and configure the DevTools all in one go with the following Angular CLI command:
generate store AppState –root –module app.module.ts
In order for this command to work, we will need first to enable Ngrx Schematics, by adding this Angular CLI configuration:
ng config cli.defaultCollection @ngrx/schematics
After running these commands, we will see that the Ngrx DevTools are enabled in the root application module, but only if the Angular CLI is running in development mode:
And with this, our application now supports the Ngrx DevTools! Now we just have to install the DevTools extension in our browser, by following these instructions.
After installing the extension, you now should have the Ngrx DevTools available under the “Redux” menu option of your browser development tools (open them with Ctrl+Shift+I in Chrome).
After opening the Ngrx DevTools, you will have to reload the application in order to start debugging the application.
Setting up the Router integration (ngrx-router) from the beginning
With the Ngrx DevTools, having the browser extension up and running is only half the story. As soon as we start using the DevTools, we will run into scenarios where the time-traveling feature comes in handy.
But the time-traveling debugger by default cannot navigate through multiple application screens, so we can’t use it to effectively replay the complete user UI session from the beginning.
In order to enable full time-traveling debugging, we need to somehow integrate the DevTools with the Angular Router, so that going back in the Action timeline also means navigating to previous screens.
The Ngrx Router Store module allows us to do exactly that, so let’s go ahead and enable it in our root application module.
Installing the Ngrx Router Store module
In order to enable the Ngrx Store router integration, we need to first declare the following in the root module imports section:
This configuration means that the Router Store module will save its state inside the store under an application state property named router (configured via the stateKey).
Setting up the Router Store reducer
In order to populate the store with this new router state, we will need a new reducer for handling all state under the router property. We can configure this reducer in our root reducer map in the following way:
And with this, we have finished the setup of the Ngrx Router Store module! Let’s now have a look at how the Router Store integration works in practice.
The Ngrx Router Store in Action
Now as we restart our DevTools, we will see that router navigation occurrences now show up as actions in the Action Log:
If we inspect the content of the action logged as ROUTER_NAVIGATION using the Ngrx DevTools, we see that it contains all the information necessary for performing a router transition:
Also, if we inspect the state of the store, we will see that there is now some new state under the router property:
As we can see, now with each router navigation the Ngrx Router Store module is capturing the router state and saving it inside the store, under the router property!
This will allow us the replay all the actions of the user from the beginning of the debugging session, including router navigations.
How important is the Ngrx Router Store module, is it optional?
It might look like the Ngrx Router Store module is optional.
But if you really want to be able to use the Ngrx DevTools in a lot of practical scenarios where often router navigation is involved, then this module ends up becoming essential.
Note that although this module would potentially also allow us to trigger router navigation by dispatching store actions (see here to learn how), that is not the main goal of the module.
The main goal of the Router Store module is not to replace the Angular Router navigation API.
Instead, its main goal is to enable the DevTools and time-traveling debugging to work well in a much larger number of scenarios where routing is involved, although having the router state in the store might also come in handy in other situations.
Using a custom router serializer in order to avoid freezing the DevTools
If you are using an Ngrx version earlier than this one, you will have likely noticed that the Ngrx DevTools might crash due to unresponsiveness problems.
This was caused due to problems in attempting to serialize the Angular Router state, which by default contains cycles in its object graph.
This means that in order to solve this issue and have fully functioning Ngrx DevTools, you might have to install a custom Router state serializer, that stores the router state in a format without graph cycles.
This unresponsiveness/crash problem is solved in newer releases, but if you still have it in your application then its better to quickly install a custom router serializer, in just a few steps.
Here are the instructions on how to install the custom router serializer.
Note: for earlier Ngrx releases that had this DevTools unresponsiveness issue, using a custom router state serializer was usually not optional in practice
With this last problem solved, you should now have fully functional Ngrx Development tools, with full time traveling debugging capability!
Prevent several types of bugs by using Ngrx Store Freeze
Let’s now cover another very useful development tool that we have available in the Ngrx ecosystem: Ngrx Store Freeze.
As you know, the store reducer functions need to be written in a very precise way, and not following that well-known set of rules can be a recipe for some very hard to troubleshoot bugs.
One of those rules is that reducer functions should be pure functions, that take as input the current state and the action, and return the new state.
The reducer function is not expected to any way mutate either the existing state or the dispatched action. Instead, its expected that the reducer function returns a new version of the state without mutating any of its inputs.
Not doing so so might potentially cause some very hard to troubleshoot issues in our application, especially if we are using OnPush change detection in large parts of our component tree.
Other than that, it also breaks the time-traveling debugger feature.
Mutating the store state at the component level
Another common source of hard to debug errors while building a store application is the possibility of some component in our component tree to accidentally get a direct reference and directly mutate the store state.
Having a direct reference to the store mutable state would allow the component to directly mutate the store state and therefore break the store pattern, instead of having to dispatch an action in order to change the store state.
Mutating the store state directly either via the component tree or inside reducers also breaks the time-traveling feature of the DevTools.
This, combined with the possibility of introducing time-consuming bugs and architecture errors makes this problem something that we want to tackle from the very beginning.
As we will see, Ngrx Store Freeze provides us with a really simple solution for this very common set of potential problems.
How does Ngrx Store Freeze work?
Ngrx Store Freeze is easy to install and provides an effective solution to all the previously mentioned problems related to store state mutability.
The Ngrx Store Freeze module automatically deep freezes our full store state object as well as dispatched actions. It does so by going to each object property of the store state and setting it to read-only.
Nested properties are also recursively frozen, meaning that the whole store state object is made effectively immutable.
With this setup, it’s now impossible for reducer functions to mutate the store state or the action, both in our reducers and in our component tree.
Installing Ngrx Store Freeze
Ngrx Store Freeze is a meta-reducer, meaning that its just a normal reducer function. The difference towards a normal reducer is that a meta reducer is applied on top of the output of another reducer function.
Meta-reducers can then be combined in an ordered chain, with each meta-reducer building on top of the output of the previous meta-reducer.
The Ngrx Store Freeze meta-reducer will be applied after all the normal reducers have been triggered for a given action, and it will freeze the whole store state before the state can even be sent back to the component tree.
Here is how we install the Ngrx Store Freeze meta-reducer:
Notice that Ngrx Store Freeze is only active in development mode. In production, we won’t get the potential performance penalty linked to deep freezing the store state with each action dispatch.
Notice that this performance hit is only noticeable for a very large amount of store state
Ngrx Store Freeze in Action
Let’s then go ahead and see how Ngrx Store Freeze works! Here is an example of an incorrectly written reducer, that accidentally mutates the original store state:
As we can see, the reducer for the login action is directly mutating the original authentication feature state.
Without Ngrx Store Freeze, this might not cause any immediately visible issue, other than breaking the time-traveling debugger feature of the NGrx DevTools.
But if our application is using OnPush, this would likely already start causing view synchronization issues, where the view and the store data are no longer in sync.
Catching mutability issues from the start
In order to avoid this problem altogether, we just have to active Ngrx Store Freeze. This time around, we would get the following error in the console:
The error might look at bit intimidating at first, but looking further into the stack trace, its actually a very useful error message: notice that on the stack trace we even have the exact line of the reducer function that is causing the issue.
By refactoring the reducer in order to return a new state object instead of mutating the existing one, the problem is now fixed:
Preventing store state mutation at the component level
Note that Ngrx Store Freeze also prevents issues caused by attempting to mutate the store state directly at the component level, which would break the store architecture in general as well as the DevTools time-traveling functionality.
If you don’t have Ngrx Store Freeze in your Ngrx application, you might want to give it a go and see if you can preventively catch a couple of potential bugs in your application!
The best is to use it from the beginning and avoid altogether all these potential issues. Besides, ensuring store state immutability makes it really simple to adopt OnPush change detection everywhere in the application (if needed), which depending on the application can make for a nice UI performance boost.
Action Conventions, make the most out of the DevTools and the Store architecture
To make the most out of the Ngrx DevTools and the store architecture, it’s important to name our actions and choose their action type description string according to certain useful conventions.
Going back to our action log, notice how we can already tell something about the application just by reading the log:
Without looking at any source code, we can already tell that:
• the user logged in
• then the user navigated to the Home Page
• There, a list of courses was requested
• Eventually, the list was loaded from the backend using an API request
This log is readable because the action types of this application are written in the following way:
These action types follow the following convention:
The convention works like this:
• The Source is the part of the application that triggered the Action. For example, the screen that dispatched the Action
• The Event is the application event linked to the Action