diff --git a/projects/ngrx.io/content/guide/signals/faq.md b/projects/ngrx.io/content/guide/signals/faq.md index f4cd6df071..d2803022fb 100644 --- a/projects/ngrx.io/content/guide/signals/faq.md +++ b/projects/ngrx.io/content/guide/signals/faq.md @@ -75,4 +75,86 @@ export class CounterComponent { constructor(readonly store: CounterStore) {} } ``` + + + +
+I get the error "Cannot assign to read only property 'X' of object '[object Object]'" + +The state in the SignalStore must be immutable. If you make mutable changes, thereโ€™s a high risk of introducing subtle, hard-to-diagnose bugs. To protect against this, SignalStore introduced an additional check to enforce immutability. + +The immutability requirement originates from Angular's Signal itself, which serves as the foundation of the SignalStore. Hereโ€™s an example to illustrate this: + +```ts +const person = signal({ name: 'Konrad', age: 25 }); +const personFormat = computed( + () => `${person().name} is ${person().age} years old`, +); + +console.log(personFormat()); // shows 25 years + +person().age = 30; // ๐Ÿ‘Ž mutable change +console.log(personFormat()); // ๐Ÿ‘Ž person did not notify personFormat. still 25 years + +// another mutable change +person.update((value) => { + value.age++; // ๐Ÿ‘Ž + return value; +}); + +console.log(personFormat()); // ๐Ÿ‘Ž no notification. 25 years. + +// immutable change +person.update((value) => ({ + ...value, // ๐Ÿ‘ immutable change + age: 40, +})); +console.log(personFormat()); // ๐Ÿ‘ personFormat has been notified and shows 40 years. + +``` + +As you can see, the problem typically arises in computed, effect or the component's template, not directly at the root (the signal mutation itself). This is why these issues are so hard to debug. + +You might look into your components wondering why theyโ€™re not updating, while the real error is buried deep within the SignalStore. + +Therefore, both `signalState` and `signalStore` throw on those mutable changes. They protect you! + +```typescript +const person = signalState({ name: 'Konrad', age: 25 }); +patchState(person, (value) => { + value.age++ + return value; +}) // ๐Ÿ”ฅ throws + + +person().age = 30; // ๐Ÿ”ฅ throws +``` + +If you require mutable properties in your state, then put them into `withProps`. + +Common examples are `FormGroup` or instances from the router package: + +```ts +const PersonStore = signalStore( + withProps(() => { + const router = inject(Router); + + return { + _currentUrl$: router.events.pipe( + filter((event) => event instanceof NavigationEnd), + map(() => router.routerState.snapshot.url), + ) + }; + }), + withState({ name: 'Konrad', age: 25 }), + withMethods(store => ({ + setAge(age: number) { + store.age = age; + } + }))); +const personStore = new PersonStore(); + +personStore.setAge(30); +``` +