Refreshing a component within the ngrx/store

Currently utilizing @ngrx/store within my Angular 2 application.

The store contains a collection of Book objects. I aim to modify a specific field within one of those objects. Additionally, there is an Observable representing the Book instance I wish to update (referred to as selectedBook).

To execute the update, my plan is to invoke the reducer with an UpdateBookAction, supplying it with the new Book payload. Therefore, I create a deep copy of the current Book object by subscribing to selectedBook and employing Object.assign().

However, when attempting to edit a property of the copied object, an error arises. This error mirrors the one encountered if trying to directly modify 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 previously assumed that an action could have a payload not encompassing the entire store state. This notion appears necessary according to the documentation. The action I wish to perform looks like this:

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

Hence, I intend to make the call in this manner:

this.store.dispatch(action);

Naturally, behind the scenes, ngrx forwards my action to the reducer alongside the immutable current state. Subsequently, everything should proceed smoothly. Inside the reducer, my logic doesn't mutate the existing state but rather generates a new one based on the current state and passed-in payload.

The main concern here is constructing the "objectToUpdate" effectively to serve as the payload.

One approach I considered involves:

this.book$.subscribe(book => {

    let updatedBook = new Book();
    updatedBook.id = book.id;
    //manually set other fields...
    updatedBook.name = 'something else';

    let action = new BookUpdateAction(updatedBook);
    this.store.dispatch(action);

}

Yet, what if the Book has numerous fields? Must I laboriously build an entirely new Book from scratch each time just for updating a single field?

To address this dilemma, I opted for a deep copy using Object.assign({}, book) (ensuring no mutation occurs on the original) followed by making updates solely to the targeted field.

Answer №1

Utilizing the ngrx store involves maintaining a single source of truth where all objects are immutable, requiring full recreation for any changes. Incorporating ngrx freeze (https://github.com/codewareio/ngrx-store-freeze) ensures objects remain read-only, aligning with the redux pattern and enhancing development practices. While it's possible to alter frozen objects by removing this feature, it's not recommended.

My recommendation is to employ ngrx observables with async pipes to handle data flow in a dumb component responsible for input/output events like book editing. Within the dumb component, create copies of objects for editing before emitting changes back to the smart component subscribed to the store for state modifications via commits. This method avoids unnecessary state re-creations for minor updates, such as user input changes.

Following the redux pattern allows for implementing features like history tracking to enable UNDO functionality, simplified debugging, timeline view, etc.

The issue arises from directly modifying properties instead of recreating the entire state.

Answer №2

I will need to assume the exact situation the original poster is facing.

The issue

The problem arises when trying to alter a member of an object that has been frozen, resulting in the error being thrown.

The reason

When using ngrx-store-freeze as a meta-reducer, any object entering the store gets frozen. However, when attempting to make changes, only a shallow copy is created. It's worth noting that Object.assign() does not perform a deep copy. Modifying a member of another object linked from the original one leads to this error, as the secondary object is also frozen but not duplicated.

Potential solution

To address this issue, it is advisable to utilize a method for deep copying, such as cloneDeep() from lodash. Alternatively, pass a set of properties to be modified along with a suitable action and manage these alterations within the reducer function.

Answer №3

It has been mentioned before that the issue you are experiencing:

Cannot assign to read only property 'name' of object

is due to the fact that 'ngrx-store-freeze' freezes the state, preventing any mutations.

While Object.assign can create a new object as expected, it also copies the properties of the state along with their definitions - including the 'writable' definition (which is likely set to false by 'ngrx-store-freeze').

An alternative method is discussed in this response, which suggests using JSON.parse(JSON.stringify(yourObject)) for cloning objects, although this approach may have drawbacks if your state contains dates or methods.

For deep cloning the state, utilizing lodash's 'cloneDeep' is likely the most reliable option.

Answer №4

To achieve this task, one method that can be utilized is a utility/helper function for creating a new book instance. By providing an existing book and the specific subset of properties you wish to include in the new book (using Partial in TypeScript for type safety), you can easily customize the creation process.

createNewBook(oldBook: Book, newProps: Partial<Book>): Book {
    const newBook = new Book(); 
    for(const prop in oldBook) {
        if(newProps[prop]) {
            newBook[prop]=newProps[prop];
        } else {
            newBook[prop]=oldBook[prop];
        }
    }
    return newBook;
}

This method can be invoked by calling

newBook = createNewBook(new Book(), {title: 'first foo, then bar'});
. Subsequently, use this newly created book object to update your data store efficiently.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Don't forget to save the selected tab in Angular 5 using a bootstrap tabset

Using the tabset feature from Bootstrap (specifically ngx-bootstrap.es2015.js) in an Angular 5 application has been mostly successful. However, a common issue arises when navigating between components. When transitioning back to the component with the tabs ...

The method useImperativeHandle encounters an issue: it cannot find the property 'ref' within the type 'IntrinsicAttributes & ResultsConnectedProps'

I encountered an issue while attempting to pass a ref to a forwardedRef component. Below is the code for the forwardRef component: interface ResultsConnectedProps { sql: string; } export const ResultsConnected: FC<ResultsConnectedProps> = forwardR ...

Dynamic Angular routes with varying numbers of parameters

I am working on developing an application where I need to associate TreeList navigation with routes. Consider the file structure in the explore section: - desktop - file1.txt - pictures - wallpaper - my-selfie.png - file2.txt - file4. ...

Mastering the art of implementing md-table with services in Angular 4

I am a newcomer to the world of angular and I am attempting to utilize the new md-table component in Angular Material 2 with Angular 4. I have created a service that fetches simple arrays of content from an API. However, I am struggling to use this servic ...

I am experiencing an issue with my Angular application where it appears blank on gh-pages and is unable to load JavaScript from Travis

After uploading an Angular app with Travis on Github pages using the gh-pages branch, I'm encountering a frustrating issue. The page is blank and there are several error messages in the console: Failed to load resource: https://hdz.github.io/runtime.j ...

Error in Express application due to uncaught SyntaxError

I am attempting to live stream data using websockets to Chartjs, however I keep encountering the following error. main.js:1 Uncaught SyntaxError: Unexpected token <https://i.sstatic.net/9OCI1.png https://i.sstatic.net/bmGeW.gif What could be causi ...

Checking for queryParam changes in Angular before ngOnDestroy is invoked

I am looking to conditionally run some code in the ngOnDestroy function depending on changes in the current route. Specifically, when the route changes from /foo to /login?logout=true, and this change is initiated outside of the Foo component. In the ngO ...

The behavior of JS Array.push may catch you off guard

There seems to be an issue with the push function in my code. The problem lies in the last line of code shown below. export enum non_searchFieldsNames { language = 'language', categories = 'categories', subtitle = 'subt ...

Mastering intricate data structures using React.js

I am working on creating a table for orders using React+Redux. The data I need is stored in props and it has a structured format similar to this: [{ //stored in props(redux state) "id": 37, //order 1 "content": { "items": { " ...

I'm only appending the final element to the JavaScript array

Currently, I have the following code: I'm endeavoring to create a new JSON object named dataJSON by utilizing properties from the GAJSON object. However, my issue arises when attempting to iterate over the GAJSOn object; only its last element is added ...

Trouble Arising in Showing the "X" Symbol upon Initial Click in Tic-Tac-Toe Match

Description: I'm currently developing a tic-tac-toe game, and I've run into an interesting issue. When I click on any box for the first time, the "X" symbol doesn't show up. However, it works fine after the initial click. Problem Details: ...

Issue with Tailwind Custom Colors Not Being Applied in Angular Deployment Environment

I have a collection of colors in Tailwind that function properly with ng serve or the Angular development build. However, I am facing an issue where some colors are not displaying correctly when using the production build of the website. I attempted to pl ...

Is it possible to modify the authenticated user ID right before its creation in Firebase, especially when the user is being created via the Facebook provider?

As we transition our MongoDB database to Firebase with Firestore, we are facing the challenge of integrating Firebase authentication for our users. Currently, we store user data in Firestore and want to utilize Firebase authentication for user logins. Each ...

Passing Data Between Page and Component (NEXT.JS + LEAFLET Integration)

I have recently started using Next.js with Leaflet maps and have encountered a beginner's question. I created a page in Next.js ( /pages/map/[id].jsx ) that utilizes a component ( /component/Map.jsx ). Within the page ( [id].jsx ), I fetch a JSON fil ...

Having difficulty accessing information from Firebase database using the .once() function

When a button is clicked on the page, I need to fetch data from a Firebase database using the once() function. Despite setting up the necessary references and variables, the data retrieval seems to be unsuccessful as the global variable numElections keeps ...

The send_keys function in Selenium with Python operates exactly as intended

I have encountered an issue with uploading files in HTML using Selenium WebDriver. The element.send_keys(absfilepath) method does not work as expected. When I send a click command to the element, it opens the file upload window on my Linux operating system ...

Is there a way to determine the monitor's frame rate using JavaScript?

Could JavaScript be used to determine the refresh rate of a monitor, typically set at 60Hz for most LCD monitors? Is there a method available to execute a function after a specific number of frames have passed? There has been some curiosity about my reaso ...

Utilizing the Directive to Enhance Focus on Input Field

At the moment, I am utilizing a directive that focuses on an input field. It works perfectly fine when I first enter the page. However, it seems that this method is not triggered when navigating back or popping to a specific page. My assumption is that ngA ...

Incorporate the function's output into an <img> tag within an

I am trying to utilize the output of a JavaScript function to populate the 'src' attribute of IMG tags. I plan to implement this in multiple locations on the same page, otherwise I could have used an ID in the IMG tag and update src with getEleme ...

Alternating row colors using CSS zebra striping after parsing XML with jQuery

After successfully parsing XML data into a table, I encountered an issue with applying zebra stripe styling to the additional rows created through jQuery. Despite my efforts to troubleshoot the problem in my code, I remain perplexed. Below is a snippet of ...