On this page ▾
Restore NgRx signal state
The NgRx Store tab can put a signal store back to its state after any change in the log. By default it writes the state signals directly. That updates your components, but watchState listeners do not run.
Register patchState once, and restore goes through it instead. Then watchState listeners run as they would for any other change.
Why it matters
A watchState listener runs on every state change made through patchState. Stores often use one to save state or sync it somewhere else:
// travel.store.ts
import {signalStore, watchState, withHooks, withState} from '@ngrx/signals';
export const TravelStore = signalStore(
{providedIn: 'root'},
withState({query: '', saved: [] as string[]}),
withHooks({
onInit(store) {
watchState(store, (state) => {
localStorage.setItem('travel', JSON.stringify(state));
});
},
}),
);
Without registerNgrxSignals, a restore changes the store but skips this listener. With it, the listener runs.
Register patchState and watchState
Call registerNgrxSignals({ patchState, watchState }) from @santoshyadavdev/ng-devtools/overlay once, after the app starts. Load both modules with dynamic imports in development only, so production bundles do not include the devtools.
// src/main.ts
import {bootstrapApplication} from '@angular/platform-browser';
import {appConfig} from './app/app.config';
import {App} from './app/app';
bootstrapApplication(App, appConfig)
.then((ref) => {
if (typeof ngDevMode === 'undefined' || ngDevMode) {
return ref
.whenStable()
.then(() =>
Promise.all([import('@santoshyadavdev/ng-devtools/overlay'), import('@ngrx/signals')]),
)
.then(([devtools, {patchState, watchState}]) =>
devtools.registerNgrxSignals({patchState, watchState}),
);
}
return undefined;
})
.catch((err) => console.error(err));// src/main.ts
import {bootstrapApplication} from '@angular/platform-browser';
import {App} from './app/app';
import {appConfig} from './app/app.config';
bootstrapApplication(App, appConfig).then(async () => {
if (import.meta.env.DEV) {
const [devtools, {patchState, watchState}] = await Promise.all([
import('@santoshyadavdev/ng-devtools/overlay'),
import('@ngrx/signals'),
]);
devtools.registerNgrxSignals({patchState, watchState});
}
});The Angular CLI version is the demo app's src/main.ts. It loads the overlay and registers both functions in the same step.
Registering watchState gives you one log entry per patchState call, including several calls in the same tick. Without it, the overlay batches writes per microtask into one entry.
Restore a state
What happens
Every state key of the store goes back to its value right after that change. Components that read the store update at once. The log gets a Restore entry.
patchState registered, the log entry says that watchState listeners were not notified.
Limits
Signal stores
- Restore needs every state key to be writable. It can't restore a read-only store state.
- The log keeps the last 200 entries per page. You can't restore older changes.
@ngrx/store
For @ngrx/store, restore uses Store DevTools instead. Add provideStoreDevtools() to enable it.
// src/app/app.config.ts
import {ApplicationConfig} from '@angular/core';
import {provideStoreDevtools} from '@ngrx/store-devtools';
export const appConfig: ApplicationConfig = {
providers: [provideStoreDevtools()],
};
provideStoreDevtools(), action log entries cannot be restored.