DEV Community

Cover image for Debugging NgRx in NativeScript with Redux DevTools
Valor-software-tech
Valor-software-tech

Posted on • Updated on

Debugging NgRx in NativeScript with Redux DevTools

The solution and the story were kindly provided by Eduardo Speroni, a NativeScript developer at Valor Software, a talented open-source activist and contributor.

Image description

Intro
NgRx needs practically no introduction in the current state of Angular applications. It allows you to manage your app’s state easily and make your UI react to the changes seamlessly. This tool is not exclusively for the web, so we can expect many NativeScript Angular applications to use it quite extensively.

Although NgRx is amazing, it still adds certain complexity to your application that is often harder to debug. For such cases, using Redux DevTools becomes indispensable in the debugging process, as it allows you to have a clear view of your state and how it changes over time. Those features, alongside time travel and custom action dispatch, make it a very powerful tool.

Many stacks have to implement their own way of integrating NgRx with Redux DevTools, for example, with Ionic, as Zack Barbuto describes in his article about remote debugging. Also, thanks to our recent efforts on WebSockets for NativeScript (find the creation story on the blog) and the chain webpack configuration added in @nativescript/webpack 5+, you can debug NgRx in NativeScript with ease. In this article I’ll tell you about our implementation details and how you can start using it right away.

Table of Contents:

  1. Intro

  2. Connecting Redux DevTools with NgRx for NativeScript

  3. Overriding the way NgRx fetches the Redux extension implementation

  4. Nativescript-ngrx-devtools plugin: features review

  5. How to use the Plugin, steps to reproduce for debugging

  6. Caveats

  7. Useful Links

Connecting Redux DevTools with NgRx for NativeScript

NgRx relies on the Redux DevTools Extension to be defined in the window and implement a specific (and relatively simple) interface. Redux Remote DevTools provides an option to connect to it remotely through SocketCluster, which uses WebSockets. Thankfully, we can now use the WebSockets plugin to polyfill them.

This approach is not new, and part of our implementation can be credited to Zalmoxis and his remote debugging method. Not all node or browser libraries support NativeScript, but we can make most of them work with it. Since NS isn’t either “node” or “browser” but a combination of multiple APIs common to both, libraries that support both platforms might be usable here. SocketCluster is one of those libraries that work perfectly with NativeScript as long as we use the browser implementation. As a result, we implemented a custom way of reading the browser field spec, which is also nicely described on npm and applied the required file substitutions individually for the required libraries. This means we can now use the web version of SocketCluster freely in our implementation without fear that it’ll break existing applications.

Taking inspiration from previously mentioned implementations of the extension interface, we built our own interface focusing on reliability and small footprint. This was done by converting it fully to RxJS, adding error handling, retrying failed connections, trying to connect to multiple default debugging IPs periodically, and making it all highly configurable. The result was a robust bridge between your application and the remote Redux DevTools so that you can focus less on the details and more on developing your application faster.

Overriding the Way NgRx Fetches the Redux Extension Implementation

Once we finished writing the Redux DevTools Extension interface, we needed to provide it to NgRx. Unfortunately, NgRx gets this information from the [‘REDUX_DEVTOOLS_EXTENSION’] window, which isn’t available in NativeScript. Polyfilling “window” is not the best approach either, as it could also lead to unintended side effects due to many libraries checking if a window exists to determine if they’re running in a browser environment or not. With a PR to NgRx, we were able to export the required symbol to override the way NgRx fetches the extension implementation.

As a side note, this change isn’t retroactive, but the plugin code has a workaround for cases when this symbol is not defined, so you can use it with older versions.

Nativescript-ngrx-devtools Plugin: Features Review

Below is the overview of key plugin features, so you get to know it better before the installation.

The beginning of work with the plugin:

Image description

Plugin replays the state after skipping actions:

Image description

Image description
The increment with delay:

Image description
Dispatching a custom action:

Image description

How to Use the Plugin

To start using the plugin, first ensure to install both: it and our WebSockets Polyfill:

npm i @valor/nativescript-ngrx-devtools
@valor/nativescript-websockets

In your polyfills.ts, ensure that websockets is properly polyfilled after nativescript’s globals:

/**
 * NativeScript Polyfills
 */
// Install @nativescript/core polyfills (XHR, setTimeout, requestAnimationFrame)
import '@nativescript/core/globals';
import '@valor/nativescript-websockets'; // add this line!
Enter fullscreen mode Exit fullscreen mode

You can then import the modules of your application and start using it:

@NgModule({
 imports: [
   NativeScriptModule,
   StoreModule.forRoot(
     {
       counter: reducer,
     },
     {
       initialState: {
         counter: initialState,
       },
     }
   ),
   ...(__DEV__ ? [ // ensure this code is tree shaken in prod
         StoreDevtoolsModule.instrument(), 
         NativeScriptNgRxDevtoolsModule.forRoot()
      ] : []),

 ],
 declarations: [AppComponent],
 bootstrap: [AppComponent],
 schemas: [NO_ERRORS_SCHEMA],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

By default the plugin will attempt to connect to many IP addresses that NativeScript automatically detects from NS_DEV_HOST_IPS on port 8000, but those can be configured in the options object in NativeScriptNgRxDevtoolsModule.forRoot(options).

After configuring our remote devtools, you can just start debugging!

Steps to Reproduce for Debugging:

  1. Run

npm i -g @redux-devtools/cli‍

  1. Then

    redux-devtools — open‍

  2. Open ‘Settings’ in the redux-devtools UI and ensure ‘connect local’ is checked and you’re going to use port 8000 which is default, then save those settings.

  3. Run a NativeScript app and start debugging.

Caveats

Redux DevTools already has a couple of caveats on the web, like having it crash by storing certain kinds of non-serializable objects. This also applies to NativeScript, and the fact that ignoring those caveats can also crash your app makes them even worse. Here’s an example from one of the projects we’re working on now. When testing the plugin on a large mobile app that has modals with high-definition images, we noticed that the app would crash due to a memory lack. The issue was that the action that was sent to the DevTools, contained a reference to the modal itself, so it was never garbage collected, and every time it opened, the memory would increase. Connecting to the DevTools also made the app take a performance hit as it was trying to serialize massive objects.

The solution, in this case, is to use actionSanitizer and stateSanitizer to make sure your state and actions contain only serializable data.

How it works with the plugin:

Image description

Find more on this topic from the Use sanitizers to avoid Redux Devtools crash article by Migzar Navarro and the NgRx official guide. Also worth mentioning that it’s critical to ensure you’re using webpack flags, not to bundle the DevTools in production. As they have a memory overhead you don’t want in production apps.

That’s all I wanted to tell in this article. Hope, it was useful, and you’ll enjoy using the plugin!

Useful Links

Top comments (1)

Collapse
 
meherhendi profile image
meherhendi

Thank you so much for the great article!
I finally got NGRX devtools working with Nativescript!