Skip to main content

withDevtools()

Redux Devtools is a powerful browser extension tool, that allows you to inspect every change in your stores. Originally, it was designed for Redux, but it can also be used with the SignalStore. You can download it for Chrome here.

To use the Devtools, you need to add the withDevtools() extension to your SignalStore:

export const FlightStore = signalStore(
{ providedIn: 'root' },
withDevtools('flights'), // <-- add this
withState({ flights: [] as Flight[] })
// ...
);

After that, open your app and navigate to the component that uses the store. Open the Devtools and you will see the flights store in the Devtools under the name "NgRx Signal Store"

You can find a working example in the demo app.

info

The extensions don't activate during app initialization (as it is with @ngrx/store). You need to open the Devtools and select the "NgRx Signal Store" tab to activate them.

updateState() vs patchState()

The Signal Store does not use the Redux pattern, so there are no action names involved by default. Instead, every action is referred to as a "Store Update". If you want to customize the action name for better clarity, you can use the updateState() function instead of patchState():

patchState(this.store, { loading: false });

// updateState is a wrapper around patchState and has an action name as second parameter
updateState(this.store, 'update loading', { loading: false });

renameDevtoolsName()

If multiple instances of a given SignalStore exist, the Devtools will index the names. For example, if you have two TodoDetail instances with the name todo-detail, the first one will be named todo-detail and the second one todo-detail-1.

At any time, you can use renameDevtoolsName() to change the name of the store in the Devtools.

The following example shows a component, which has a locally provided store and renames it according to the id of the todo Signal.

const TodoDetailStore = signalStore(withDevtools('todo-detail'), withState({ id: 1 }));

@Component({
// ...
providers: [TodoDetailStore],
})
export class TodoDetailComponent {
readonly #todoDetailStore = inject(TodoDetailStore);
todo = input.required<Todo>();

constructor() {
effect(() => {
renameDevtoolsName(this.#todoDetailStore, `todo-${this.todo().id}`);
});
}
}

withGlitchTracking()

It tracks all state changes of the State, including intermediary updates that are typically suppressed by Angular's glitch-free mechanism.

This feature is especially useful for debugging.

Example:

const Store = signalStore(
{ providedIn: 'root' },
withState({ count: 0 }),
withDevtools('counter', withGlitchTracking()),
withMethods((store) => ({
increase: () => patchState(store, (value) => ({ count: value.count + 1 })),
}))
);

// would show up in the DevTools with value 0
const store = inject(Store);

store.increase(); // would show up in the DevTools with value 1
store.increase(); // would show up in the DevTools with value 2
store.increase(); // would show up in the DevTools with value 3

Without withGlitchTracking(), the DevTools would only show the final value of 3.

It is also possible to mix. So one store could have withGlitchTracking() and another one not.

withDisabledNameIndices()

withDevtools() foresees the possibility to add features which extend or modify it. At the moment, withDisabledNameIndices() is the only feature available. It disables the automatic indexing of the store names in the Devtools.

If multiple instances exist at the same time, withDisabledNameIndices() will throw an error. This is useful if you want to ensure that only one instance of a store is active at a time or that the store name is unique.

You activate per store:

const Store = signalStore({ providedIn: 'root' }, withDevtools('flights', withDisabledNameIndices()), withState({ airline: 'Lufthansa' }));

withMapper()

withMapper() allows you to define a function that maps the state before it is sent to the Devtools.

Sometimes, it is necessary to map the state before it is sent to the Devtools. For example, you might want to exclude some properties, like passwords or other sensitive data.

const initialState = {
id: 1,
email: 'john.list@host.com',
name: 'John List',
enteredPassword: '',
};

const Store = signalStore(
withState(initialState),
withDevtools(
'user',
withMapper((state) => ({ ...state, enteredPassword: '***' }))
)
);

Disabling Devtools in production

withDevtools() is by default enabled in production mode, if you want to tree-shake it from the application bundle you need to abstract it in your environment file.

It is required to add the withDevtools() function to the environment files.

environments/environment.ts:

import { withDevtools } from '@angular-architects/ngrx-toolkit';

export const environment = {
storeWithDevTools: withDevtools,
};

environments/environment.prod.ts

import { withDevtoolsStub } from '@angular-architects/ngrx-toolkit';

export const environment = {
storeWithDevTools: withDevToolsStub,
};

Then you can create utility function which can be used across the application e.g.:

shared/store.features.ts (or any other file)

import { environment } from 'src/environments/environment';

export const withTreeShakableDevTools = environment.storeWithDevTools;

And use it in your store definitions:

export const SomeStore = signalStore(withState({ strings: [] as string[] }), withTreeShakableDevTools('featureName'));

Also make sure you have defined file replacements in angular.json prod configuration:

{
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
]
}