Skip to content

Instantly share code, notes, and snippets.

What would you like to do?
Using @ngrx/store-devtools remotely with Ionic 2

Ok, I have on-device remote store debugging working with Ionic 2. Unfortunately, time-travel and state import doesn't work with store-devtools yet (see and, but at least the Inspector, Log Monitor and Graph is working remotely. Here's how:

First, add to the project:

> npm install --save-dev remotedev

Then add these devtools proxy wrapper classes to the project. They provide the same interface as the browser extension so store-devtools will think it's just talking to the chrome extension. I left in the trace debug logging so you can clearly see what's happening in the console, but it's easy to remove if you want.


import moment from 'moment';
import {
} from '@ngrx/store-devtools/src/extension';
import { connect, extractState } from 'remotedev/lib/devTools';

export class RemoteDevToolsConnectionProxy implements ReduxDevtoolsExtensionConnection {
    className = 'RemoteDevToolsConnectionProxy';

        public remotedev: any,
        public instanceId: string
    ) {

        listener: (change: any) => void
    ): any {
        const logContext = `${this.className} - subscribe`;
        console.log(`[${logContext}] listener: ${listener.toString()}`);

        const listenerWrapper = (change: any) => {
            console.log(`[${logContext} - listenerWrapper] change: ${
                JSON.stringify(change, null, 4)}`);

            // extractState handles circular references, unlike JSON.parse directly. See:
            const state = extractState(change);
            console.log(`[${logContext} - listenerWrapper] parsed state: ${
                JSON.stringify(state, null, 4)}`);

            // WARNING: @ngrx/store-devtools does NOT handle the time-travel changes
            // correctly, so this doesn't actually work yet! See the following issues:


    ): any {
        // HACK a bug in remotedev ignores the real instanceId. See:
        // UPDATE: fixed - quickly! - in remotedev@0.2.3
        //const instanceId = 0; // internal array index instead of actual this.instanceId;
        const instanceId = this.instanceId;
        console.log(`[${this.className} - unsubscribe] instanceId: ${instanceId}`);

        // HACK fix bug in @ngrx/store-devtools that calls this instead of returning
        // a lambda that calls it when their Observable wrapper is unsubscribed.
        return () => this.remotedev.unsubscribe(instanceId);

    // NOTE: THIS IS NEVER CALLED - see send() on RemoteDevToolsProxy below
        action: any,
        state: any
    ): any {
        console.log(`[${this.className} -send]\n` +
            `action: ${JSON.stringify(action, null, 4)},\n` +
            `state: ${JSON.stringify(state, null, 4)}`);

        this.remotedev.send(action, state);

export class RemoteDevToolsProxy implements ReduxDevtoolsExtension {
    className = 'RemoteDevToolsProxy';
    remotedev: any = null;
    defaultOptions = {
        realtime: true,
        hostname: 'localhost',
        port: 8000,
        autoReconnect: true,
        connectTimeout: 20000,
        ackTimeout: 10000,
        secure: true,

        defaultOptions: Object
    ) {
        this.defaultOptions = Object.assign(this.defaultOptions, defaultOptions);

        options: {
            shouldStringify?: boolean;
            instanceId: string;
    ): ReduxDevtoolsExtensionConnection {
        const logContext = `${this.className} - connect`;
        console.log(`[${logContext}] options: ${JSON.stringify(options, null, 4)}`);

        const connectOptions = Object.assign(this.defaultOptions, options);
        console.log(`[redux extension - connect] connectOptions: ${
            JSON.stringify(connectOptions, null, 4)}`);

        this.remotedev = connect(connectOptions);
        console.log(`[${logContext}] remotedev:`);

        const connectionProxy = new RemoteDevToolsConnectionProxy(
            this.remotedev, connectOptions.instanceId);

        return connectionProxy;

        action: any,
        state: any,
        shouldStringify?: boolean,
        instanceId?: string
    ): any {
        console.log(`[${this.className} - send]\n` +
            `action: ${JSON.stringify(action, null, 4)},\n` +
            `state: ${JSON.stringify(state, null, 4)},\n` +
            `shouldStringify: ${shouldStringify}, instanceId: ${instanceId}`

        this.remotedev.send(action, state);

Then in your app.module.ts, just add an instance to window.devToolsExtension (legacy) and/or window.__REDUX_DEVTOOLS_EXTENSION__ (current) if you're not in a browser where it already exists (see deprecation of window.devToolsExtension in Then call StoreDevtoolsModule.instrumentOnlyWithExtension(), as usual.

app.module.ts (excerpt)

// ...
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { RemoteDevToolsProxy } from './remote-devtools-proxy'; // our new wrapper class
// ...

// Register our remote devtools if we're on-device and not in a browser
if (!window['devToolsExtension'] && !window['__REDUX_DEVTOOLS_EXTENSION__']) {
    let remoteDevToolsProxy = new RemoteDevToolsProxy({
        connectTimeout: 300000, // extend for pauses during debugging
        ackTimeout: 120000,  // extend for pauses during debugging
        secure: false, // dev only

    // support both the legacy and new keys, for now
    window['devToolsExtension'] = remoteDevToolsProxy;
    window['__REDUX_DEVTOOLS_EXTENSION__'] = remoteDevToolsProxy;
// ...
    imports: [
        // ...
        // the devtools looks for a window.devToolsExtension to attach to,
        // which we registered above if there wasn't one already.
        // ...
    // ...
export class AppModule {}

Whew, ok now that we have the remote client set up, let's run a server for it to talk to. I just installed remotedev-server globally and ran it like this:

> npm install -g remotedev-server
> remotedev --hostname=my.local.ip.address --port=8000

Make sure the hostname matches what was configured in app.module.ts. The default is localhost. Once that's up and listening for connections, we can use the Chrome Redux DevTools extension to connect to the remotedev server via the Remote button in the interface.

Now, run the app on the device. For an Android device connected via USB, I use Chrome's port forwarding feature to forward port 8000 to my dev box.


This comment has been minimized.

Copy link

commented May 11, 2017

Hi, do you still have to use this as a workaround?
Are you using this with OSX and running iOS? I am using Inonic and trying to debug my ngrx state.


This comment has been minimized.

Copy link

commented Jun 19, 2017

Is it possible to have this same workaround for Nativescript?


This comment has been minimized.

Copy link

commented Apr 27, 2018

thats awesome! - just managed to do it on macos / iOS using ngrx 4.4.1 and ionic 4.3
the only modification - I use IP address suggested by ionic cordova run ios --device --livereload --debug (for both RemoteDevToolsProxy and remotedev --hostname)


This comment has been minimized.

Copy link

commented Oct 7, 2018


This comment has been minimized.

Copy link

commented Nov 16, 2018

@rob3c How about nativescript?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.