From ed985d469f33733a80c057f17e8f72f0233a2228 Mon Sep 17 00:00:00 2001 From: Rainer Hahnekamp Date: Wed, 18 Sep 2024 15:43:23 +0200 Subject: [PATCH] feat: apply `Object.freeze` in `patchState` on state in dev mode --- modules/signals/spec/signal-state.spec.ts | 10 ++++++++++ modules/signals/src/state-source.ts | 14 +++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/modules/signals/spec/signal-state.spec.ts b/modules/signals/spec/signal-state.spec.ts index c509d07f04..81b3416818 100644 --- a/modules/signals/spec/signal-state.spec.ts +++ b/modules/signals/spec/signal-state.spec.ts @@ -194,4 +194,14 @@ describe('signalState', () => { expect(stateCounter).toBe(3); expect(userCounter).toBe(1); })); + + it('throws on a mutable change', () => { + const userState = signalState(initialState); + expect(() => + patchState(userState, (state) => { + state.ngrx = 'mutable change'; + return state; + }) + ).toThrowError("Cannot assign to read only property 'ngrx' of object"); + }); }); diff --git a/modules/signals/src/state-source.ts b/modules/signals/src/state-source.ts index 940e2d5f81..62dcc7dc12 100644 --- a/modules/signals/src/state-source.ts +++ b/modules/signals/src/state-source.ts @@ -10,6 +10,8 @@ import { import { SIGNAL } from '@angular/core/primitives/signals'; import { Prettify } from './ts-helpers'; +declare const ngDevMode: boolean; + const STATE_WATCHERS = new WeakMap>>(); export const STATE_SOURCE = Symbol('STATE_SOURCE'); @@ -40,7 +42,9 @@ export function patchState( updaters.reduce( (nextState: State, updater) => ({ ...nextState, - ...(typeof updater === 'function' ? updater(nextState) : updater), + ...(typeof updater === 'function' + ? updater(freezeInDevMode(nextState)) + : updater), }), currentState ) @@ -49,6 +53,14 @@ export function patchState( notifyWatchers(stateSource); } +function freezeInDevMode(value: State): State { + if (ngDevMode) { + Object.freeze(value); + } + + return value; +} + export function getState( stateSource: StateSource ): State {