Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(signals): Remove the protection for state mutation in dev mode #4686

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 0 additions & 150 deletions modules/signals/spec/deep-freeze.spec.ts

This file was deleted.

48 changes: 0 additions & 48 deletions modules/signals/src/deep-freeze.ts

This file was deleted.

3 changes: 1 addition & 2 deletions modules/signals/src/signal-state.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { signal } from '@angular/core';
import { STATE_SOURCE, WritableStateSource } from './state-source';
import { DeepSignal, toDeepSignal } from './deep-signal';
import { freezeInDevMode } from './deep-freeze';

export type SignalState<State extends object> = DeepSignal<State> &
WritableStateSource<State>;

export function signalState<State extends object>(
initialState: State
): SignalState<State> {
const stateSource = signal(freezeInDevMode(initialState as State));
const stateSource = signal(initialState as State);
const signalState = toDeepSignal(stateSource.asReadonly());
Object.defineProperty(signalState, STATE_SOURCE, {
value: stateSource,
Expand Down
10 changes: 4 additions & 6 deletions modules/signals/src/state-source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
WritableSignal,
} from '@angular/core';
import { Prettify } from './ts-helpers';
import { freezeInDevMode } from './deep-freeze';

const STATE_WATCHERS = new WeakMap<Signal<object>, Array<StateWatcher<any>>>();

Expand Down Expand Up @@ -38,11 +37,10 @@ export function patchState<State extends object>(
): void {
stateSource[STATE_SOURCE].update((currentState) =>
updaters.reduce(
(nextState: State, updater) =>
freezeInDevMode({
...nextState,
...(typeof updater === 'function' ? updater(nextState) : updater),
}),
(nextState: State, updater) => ({
...nextState,
...(typeof updater === 'function' ? updater(nextState) : updater),
}),
currentState
)
);
Expand Down
11 changes: 4 additions & 7 deletions modules/signals/src/with-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
SignalStoreFeature,
SignalStoreFeatureResult,
} from './signal-store-models';
import { freezeInDevMode } from './deep-freeze';

export function withState<State extends object>(
stateFactory: () => State
Expand All @@ -36,12 +35,10 @@ export function withState<State extends object>(

assertUniqueStoreMembers(store, stateKeys);

store[STATE_SOURCE].update((currentState) =>
freezeInDevMode({
...currentState,
...state,
})
);
store[STATE_SOURCE].update((currentState) => ({
...currentState,
...state,
}));

const stateSignals = stateKeys.reduce((acc, key) => {
const sliceSignal = computed(
Expand Down
43 changes: 0 additions & 43 deletions projects/ngrx.io/content/guide/migration/v19.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,49 +23,6 @@ Version 19 has the minimum version requirements:

### Signals

#### Throw error in dev mode on state mutation

The `patchState` method applies a deep freeze on the state in dev mode.
If you try to update the state directly, it will throw an error in dev mode.

BEFORE:

```ts
const userState = signalState(initialState);
patchState(userState, (state) => {
// mutable change which went through
state.user.firstName = 'mutable change';
return state;
});
```

AFTER:

```ts
const userState = signalState(initialState);
patchState(userState, (state) => {
// mutable change throws in dev mode
state.user.firstName = 'mutable change';
return state;
});
```

To fix the error, update the state in an immutable way.

```ts
const userState = signalState(initialState);
patchState(userState, (state) => {
return {
...state,
user: {
...state.user,
// immutable change which went through
firstName: 'immutable change',
},
};
});
```

#### `computed` is replaced with `props`

To support more cases, the `props` property is added to `signalStoreFeature`, which replaces the existing `computed` property.
Expand Down
1 change: 0 additions & 1 deletion projects/ngrx.io/content/guide/signals/signal-state.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@ patchState(
<div class="alert is-critical">

Updaters passed to the `patchState` function must perform state updates in an immutable manner.
If a mutable change occurs to the state object, an error will be thrown in development mode.

</div>

Expand Down
Loading