Angular 22 + NgRx: Enabling Store DevTools at Runtime (The Modern Way)

Back in 2020, I wrote an article about dynamically enabling NgRx Store DevTools at runtime without requiring the application to start with DevTools enabled.

At the time, Angular still relied heavily on NgModules, and the solution used Angular’s runtime compiler to create a module containing StoreDevtoolsModule.instrument() and inject it into the running application.

It worked beautifully.

Fast forward to Angular 22…

The Angular ecosystem has changed dramatically.

  • Standalone APIs replaced NgModules.
  • provideStoreDevtools() replaced StoreDevtoolsModule.instrument().
  • Runtime compilation is no longer part of normal Angular applications.
  • Environment providers are created during application bootstrap.

So naturally, I asked the same question:

Can we still enable NgRx Store DevTools on demand in production?

The answer is yes—just not the same way.

Let’s look at the modern solution.


Why the Old Approach No Longer Works

My original article used code similar to this:

this.compiler.compileModuleSync(DevToolsModule);

which dynamically created an NgModule containing:

StoreDevtoolsModule.instrument(...)

Because Angular applications were NgModule-based, this worked perfectly.

Today, applications typically use:

bootstrapApplication(AppComponent, appConfig);

with providers like:

provideStore();
provideStoreDevtools();

These providers become part of the root application injector.

Once Angular has bootstrapped, the provider graph is fixed.

That means this no longer works:

{
    path: 'admin',
    providers: [
        provideStoreDevtools(...)
    ]
}

Route providers create a child injector, not a new application injector.

The Store DevTools extension hooks into the root store during application startup, so adding it later has no effect.


The New Approach

Instead of trying to inject DevTools into an already running application, we simply remember that the user requested DevTools and allow the application to bootstrap with them enabled.

The entire process looks like this:

User clicks Enable DevTools
        │
        ▼
Save flag in sessionStorage
        │
        ▼
Reload application
        │
        ▼
Bootstrap reads sessionStorage
        │
        ▼
Conditionally registers provideStoreDevtools()

From the user’s perspective, DevTools are enabled on demand.

Internally, Angular simply starts with a different provider configuration.


Why sessionStorage?

I recommend using sessionStorage instead of localStorage.

export const DEVTOOLS_KEY = 'clinicx.devtools';

Enable DevTools:

sessionStorage.setItem(DEVTOOLS_KEY, 'true');
location.reload();

Disable DevTools:

sessionStorage.removeItem(DEVTOOLS_KEY);
location.reload();

Why sessionStorage?

  • survives page reloads
  • automatically clears when the browser tab closes
  • doesn’t accidentally leave DevTools enabled forever
  • perfect for debugging sessions

Conditionally Register DevTools

Now we simply read the flag during application startup.

import { provideStoreDevtools } from '@ngrx/store-devtools';

const enableDevtools =
    sessionStorage.getItem('clinicx.devtools') === 'true';

Then inside app.config.ts:

export const appConfig: ApplicationConfig = {
    providers: [

        provideStore(),

        ...(enableDevtools
            ? [
                provideStoreDevtools({
                    maxAge: 25,
                    autoPause: true,
                    logOnly: false,
                })
            ]
            : [])
    ]
};

No hacks.

No runtime compilation.

Just Angular’s normal bootstrap process.


Adding an Admin Toggle

For my ClinicX Talent project, I wanted a hidden diagnostics page available only to administrators.

The toggle becomes extremely simple:

toggleDevtools() {

    if (this.enabled()) {

        sessionStorage.removeItem('clinicx.devtools');

    } else {

        sessionStorage.setItem('clinicx.devtools', 'true');

    }

    location.reload();
}

That’s it.

The next bootstrap automatically configures NgRx correctly.


Production Considerations

This approach is intended for trusted administrators and support engineers.

If you’re deploying to production, don’t rely solely on a client-side session key.

Instead, combine the session flag with something your server validates, such as:

  • authenticated administrator role
  • short-lived signed debug token
  • feature flag service
  • internal support account

That ensures only authorized users can enable diagnostic tooling.


What About Route Providers?

One thing I tried first was adding DevTools directly to the route:

{
    path: 'deployments',
    providers: [
        provideStoreDevtools(...)
    ]
}

It seems logical.

Unfortunately, it doesn’t work.

The route gets its own injector, but the Store DevTools extension must connect to the application’s root store during bootstrap.

By the time a route activates, that opportunity has already passed.


Is This Really “Runtime”?

Technically…

No.

Angular still bootstraps a new application.

Practically…

Yes.

From the user’s perspective:

  1. Click Enable DevTools
  2. Application refreshes
  3. Redux DevTools immediately appears

The experience is almost identical to the runtime activation technique we used years ago, but it’s implemented in a way that aligns with Angular’s standalone architecture instead of fighting it.


Final Thoughts

One of the things I appreciate about Angular is that even as the framework evolves, the core ideas remain the same.

The implementation details have changed:

  • NgModules → Standalone APIs
  • StoreDevtoolsModule.instrument()provideStoreDevtools()
  • Runtime compiler → Conditional application bootstrap

But the end result is still a clean, maintainable solution.

Sometimes the best way to modernize old techniques isn’t to force yesterday’s patterns into today’s framework—it’s to embrace the new architecture and let it work for you.

Happy debugging!

— Kate

One Thought to “Angular 22 + NgRx: Enabling Store DevTools at Runtime (The Modern Way)”

  1. […] Angular NGRX enabling DevTools at runtime ( new version here) […]

Leave a Comment