Updating an object in ngrx / store

I am using @ ngrx / store for an Angular 2 application.

My store contains a list of Book objects. I want to update a field in one of these objects. I also have an observable copy of the book that I am looking for for an update (e.g. selectedBook ).

To perform an update, I intend to invoke the reducer using UpdateBookAction and the payload of a new book. So I make a deep copy of an existing Book object, subscribing to selectedBook , and then calling Object.assign ().

But when I try to write copies to one of the fields, I get the following error. (This happens with the same error that I get if I try to write directly to the book object in the store.)

Error

 Cannot assign to read only property 'name' of object '#<Object>' at ViewWrappedError.BaseError [as constructor] 

code

 ngOnInit() { this.book$ = this.store.let(fromRoot.getSelectedBook); //... } someFunction() { //... this.book$.subscribe(book => { let updatedBook = Object.assign({}, book); updatedBook.name = 'something else'; // <--- THIS IS WHAT THROWS let action = new BookUpdateAction(updatedBook); this.store.dispatch(action); } } 

Clarification after comments

I was on the assumption that I could have an action with a payload, which was not the whole state of the store. (In fact, this seems necessary, no?) I'm sure this is, given the documentation.

The action I'm looking for looks something like this:

 Action = UPDATE, payload = {'id': 1234, 'name': 'something new'} 

As already mentioned, I intend to make this call as follows:

 this.store.dispatch(action); 

Presumably under the hood, ngrx passes my action to the reducer along with the (immutable) current state.

So, from there everything should work fine. My logic inside the gearbox does not mutate the existing state, it just creates a new one from the existing state and the payload that I went through.

The real question here is how can I intelligently build a new "objectToUpdate" so that I can pass this as a payload.

I could do something like this:

 this.book$.subscribe(book => { let updatedBook = new Book(); updatedBook.id = book.id; //set all other fields manually... updatedBook.name = 'something else'; let action = new BookUpdateAction(updatedBook); this.store.dispatch(action); } 

But we are not just talking about two fields here ... what if my book has several fields? Do I have to manually create a new book from scratch each time only to update one field?

My solution was to make a deep copy using Object.assign({}, book) (and not mutate the old one!), And then do the update only in the field I was looking for.

+5
source share
3 answers

The idea of ​​the ngrx store is to have one and only one place of truth, which means that all objects are immutable, and the only way to change anything is to recreate everything as a whole. Also, you are probably using ngrx freeze ( https://github.com/codewareio/ngrx-store-freeze ), which means that all objects will be created read-only, so you won’t be able to change any (this is good for development if you want to fully follow the abbreviation pattern). If you delete the part in which the store freezes the object, you can change it, but this is not the best practice.

I suggest you the following: Use ngrx observable with an asynchronous channel to put data (in your books) into a mute component that can only input and output some kind of event. Then, inside the silent component, you can “edit” this object by making a copy of it, and after you finish, you can undo the changes in the smart component that is subscribed to the store and allow it to change state through the store (commit). This method is best because it is not very common to change the whole state for a really small change (for example, two-way binding when the user enters ...).

If you follow the redux pattern, you can add history, which means that the repository will store copies of the latest X-states, so you can get UNDO functionality, simplify debugging, timeline, etc.

Your problem is that you are directly editing the property instead of recreating the entire state.

+5
source

I need to make an assumption about the actual scenario that the OP is testing.

Problem

Unable to modify the frozen item. His mistake was reset.

Cause

ngrx-store-freeze used as a meta-reducer to freeze any object that enters the repository. In another place, when an object needs to be changed, a shallow copy is made. Object.assign() does not execute a deep copy . The member of another object reached from the original object changes. This secondary object is also frozen because it is not duplicated.

Decision

Use a deep copy, such as cloneDeep() , from lodash. Or sent a bag of properties that needs to be changed using the correct action. Process the changes on the gearbox.

+4
source

As already mentioned - the reason you get

It is not possible to assign only the property name "name" to an object

lies in the fact that "ngrx-store-freeze" freezes the state and prevents its mutation.

Object.assign will provide a new object as you expect, but it will copy the state properties along with each of its own property definitions, such as the definition of “writable” (which “ngrx-store-freeze 'probably sets to false).

This answer describes a different approach and explains how to clone objects with JSON.parse (JSON.stringify (yourObject)) as quickly as possible, but this approach has disadvantages if you save dates or methods, etc. in his condition.

Using lodash 'cloneDeep' is probably the best choice for deep state cloning.

+1
source

Source: https://habr.com/ru/post/1260613/


All Articles