File tree 3 files changed +77
-1
lines changed
3 files changed +77
-1
lines changed Original file line number Diff line number Diff line change @@ -194,4 +194,28 @@ describe('signalState', () => {
194
194
expect ( stateCounter ) . toBe ( 3 ) ;
195
195
expect ( userCounter ) . toBe ( 1 ) ;
196
196
} ) ) ;
197
+
198
+ describe ( 'freezeInDevMode' , ( ) => {
199
+ it ( 'throws on a mutable change' , ( ) => {
200
+ const userState = signalState ( initialState ) ;
201
+ expect ( ( ) =>
202
+ patchState ( userState , ( state ) => {
203
+ state . ngrx = 'mutable change' ;
204
+ return state ;
205
+ } )
206
+ ) . toThrowError ( "Cannot assign to read only property 'ngrx' of object" ) ;
207
+ } ) ;
208
+
209
+ it ( 'throws on a mutable change' , ( ) => {
210
+ const userState = signalState ( initialState ) ;
211
+ expect ( ( ) =>
212
+ patchState ( userState , ( state ) => {
213
+ state . user . firstName = 'mutable change' ;
214
+ return state ;
215
+ } )
216
+ ) . toThrowError (
217
+ "Cannot assign to read only property 'firstName' of object"
218
+ ) ;
219
+ } ) ;
220
+ } ) ;
197
221
} ) ;
Original file line number Diff line number Diff line change
1
+ export function deepFreeze < T > ( target : T ) : T {
2
+ Object . freeze ( target ) ;
3
+
4
+ const targetIsFunction = typeof target === 'function' ;
5
+
6
+ Object . getOwnPropertyNames ( target ) . forEach ( ( prop ) => {
7
+ // Ignore Ivy properties, ref: https://github.com/ngrx/platform/issues/2109#issuecomment-582689060
8
+ if ( prop . startsWith ( 'ɵ' ) ) {
9
+ return ;
10
+ }
11
+
12
+ if (
13
+ hasOwnProperty ( target , prop ) &&
14
+ ( targetIsFunction
15
+ ? prop !== 'caller' && prop !== 'callee' && prop !== 'arguments'
16
+ : true )
17
+ ) {
18
+ const propValue = target [ prop ] ;
19
+
20
+ if (
21
+ ( isObjectLike ( propValue ) || typeof propValue === 'function' ) &&
22
+ ! Object . isFrozen ( propValue )
23
+ ) {
24
+ deepFreeze ( propValue ) ;
25
+ }
26
+ }
27
+ } ) ;
28
+
29
+ return target ;
30
+ }
31
+
32
+ function hasOwnProperty (
33
+ target : unknown ,
34
+ propertyName : string
35
+ ) : target is { [ propertyName : string ] : unknown } {
36
+ return isObjectLike ( target )
37
+ ? Object . prototype . hasOwnProperty . call ( target , propertyName )
38
+ : false ;
39
+ }
40
+
41
+ function isObjectLike ( target : unknown ) : target is object {
42
+ return typeof target === 'object' && target !== null ;
43
+ }
Original file line number Diff line number Diff line change 9
9
} from '@angular/core' ;
10
10
import { SIGNAL } from '@angular/core/primitives/signals' ;
11
11
import { Prettify } from './ts-helpers' ;
12
+ import { deepFreeze } from './deep-freeze' ;
13
+
14
+ declare const ngDevMode : boolean ;
12
15
13
16
const STATE_WATCHERS = new WeakMap < object , Array < StateWatcher < any > > > ( ) ;
14
17
@@ -40,7 +43,9 @@ export function patchState<State extends object>(
40
43
updaters . reduce (
41
44
( nextState : State , updater ) => ( {
42
45
...nextState ,
43
- ...( typeof updater === 'function' ? updater ( nextState ) : updater ) ,
46
+ ...( typeof updater === 'function'
47
+ ? updater ( freezeInDevMode ( nextState ) )
48
+ : updater ) ,
44
49
} ) ,
45
50
currentState
46
51
)
@@ -49,6 +54,10 @@ export function patchState<State extends object>(
49
54
notifyWatchers ( stateSource ) ;
50
55
}
51
56
57
+ function freezeInDevMode < State extends object > ( value : State ) : State {
58
+ return ngDevMode ? deepFreeze ( value ) : value ;
59
+ }
60
+
52
61
export function getState < State extends object > (
53
62
stateSource : StateSource < State >
54
63
) : State {
You can’t perform that action at this time.
0 commit comments