3
votes

I have a component which is a form page and one of its property is the following:

profile: Profile;

When the component load up, in the ngOnInit function I grab the profile I have inside my ngRx store and I copy it into the profile object variable I have defined above:

this.store.pipe(
  take(1),
  select(user)
).subscribe(myUser => {
  this.profile = {...myUser};
  this.profile.details.firstname = "John";
  // ... more setup code

When I try to assign the string "John" to the object I copied it fails with the error:

TypeError: Cannot assign to read only property 'firstName' of object '[object Object]'

Why does this happen? How can I solve it?

My idea is to copy the object I have inside the store, to modify it with a 2-way-binding using ngModel when the user compiles my form with all the data I need (email, first name, etc) and save the new object inside the store when the user hits the submit button (dispatching an action this time, as it should be).

This question says that the spread operator should do the job so that I can work with a copy and pass the copy as a payload when dispatching the action, but in my case it doesn't work and I don't understand why.

3
where you have defined profileInfo ? - Rahul Sharma
My mistake, it's not profileInfo, it's profile. I'm gonna correct it. - Rexam
State received from the store cannot be modified by design. With NgRx state is modified in reducers, which are triggered by actions. State modifications are centralized, and not spread across all the files in the app. This is a good thing. Now, what you really want is not to modify state object, but treat it as a blueprint, copy it and modify the copy. You'll then supply that copy back to store via some action. For example use cloneDeep as @Tiep Phan points out. - kalexi
The answer suggests use of a third party library, which seems a little odd for something likely to be a common pattern. It's a real shame that activating state immutability forces immutability for objects copied from the state and that this cannot be solved simply with the tools provided by ngrx itself. - Peter David Carter

3 Answers

4
votes

NgRx store freeze all the state object. to mutate that, you should using clone deep, not shallow clone:

Example with lodash.clonedeep

https://lodash.com/docs/4.17.15#cloneDeep

https://www.npmjs.com/package/lodash.clonedeep

import cloneDeep from 'lodash.clonedeep';

this.store.pipe(
  take(1),
  select(user)
).subscribe(myUser => {
  this.profile = cloneDeep(myUser);
  this.profile.details.firstname = "John";
  // ... more setup code
3
votes

NgRx store freezes all of the state object. To mutute an object that comes from the store you can stringify and parse the result.

this.store.pipe(
        take(1),
        select(user)
    ).subscribe(myUser => {
            this.profile = JSON.parse(JSON.stringify(myUser));
            this.profile.details.firstname = "John";
            // ... more setup code
1
votes

If you are using ngrx-9, it's due to ngrx-store-freeze, it's turned on by default

From Breaking Changes notes of ngrx, you can opt-out immutability check by doing this

@NgModule({
  imports: [
    StoreModule.forRoot(reducers, {
      runtimeChecks: {
        strictStateImmutability: false,
        strictActionImmutability: false,
      },
    }),
  ],
})
export class AppModule {}

Reference https://ngrx.io/guide/migration/v9