Is it advisable to standardize the state within an Angular application?

Currently, our team is delving into our inaugural Angular projects with ngrx-store. We find ourselves deliberating whether the state should be normalized or not, sparking varying viewpoints.

Proponents of a nested store argue that it would streamline maintenance and usage, especially considering the API already delivers nested data and occasionally necessitates a complete clearing of an object from the store.

For instance: Let's consider a feature store named customer. This store encompasses customer-lists and selected customers. Notably, the selected customer object comprises more properties compared to customer objects in the list.

We exhibit the customer list (model: CustomerList) stored in the component, permitting users to navigate to a detailed page showcasing customer specifics (model: CustomerDetail). Within this detail page, users can manage various customer sublists (such as addresses, phones, faxes, etc.). Upon exiting the detail view back to the list, the stored customer object should reset.

export interface CustomerState {
    customers: CustomerList[],
    customer: CustomerDetail
};

export interface CustomerList {
    customerId: number;
    name: string;
};

export interface CustomerDetail {
    customerId: number;
    firstName: string;
    lastName: string;
    addressList: CustomerDetailAddressList[];
    phoneList: CustomerDetailPhoneList[];
    emailList: CustomerDetailEmailList[];
    faxList: CustomerDetailFaxList[];
    /* ... */
};

Consider a scenario where a user creates a new address for a customer on the specific customer's details page. The address is then posted to the API, following which, upon successful response, the store fetches the updated addressList from the API and inserts it into the customer's addressList within the store.

Opponents of a non-normalized state highlight its major drawback:

The deeper the nesting, the more intricate and lengthy the mappings become inside the store for setting or retrieving data.

On the flip side, proponents of normalization question the benefits of eliminating nesting within the customer object. In their perspective, normalizing the state could entail complexities due to differing structures between customerList and customerDetail objects. If both objects were identical, simply storing the selected customer's ID and fetching data from the customer list by ID would suffice.

Another concern raised is that, in a normalized state, leaving a customer detail page would require clearing not just the customer object but also any related lists tied to the customer.

export interface CustomerState {
    customers: CustomerList[],
    customer: CustomerDetail,
    customerAddressList: CustomerDetailAddressList[];
    customerPhoneList: CustomerDetailPhoneList[];
    customerEmailList: CustomerDetailEmailList[];
    customerFaxList: CustomerDetailFaxList[];
};

tl;dr

We are curious about your experiences and thoughts on utilizing the store - normalized or otherwise. How can we leverage the advantages of both approaches effectively? Your insights are highly valued!

If anything seems unclear or nonsensical, please do let us know. As perpetual learners, we greatly appreciate any assistance, feedback, or advice provided.

Answer №1

Your analysis of the advantages and disadvantages of both approaches is thorough and insightful - ultimately, deciding between them is a challenging decision. Storing a single object offers the benefit of updates propagating to all references of that object, but it does add complexity to your reducers.

In our intricate application, we opted for the nested approach (with some exceptions) to maintain cleaner reducer implementation. We crafted pure-function operators to modify entities like a Company:

function applyCompanyEdit(state: CompanyState, compId: number, compUpdate: (company: CompanyFull) => CompanyFull): CompanyState {
    let tab = state.openCompanies.find((aTab) => aTab.compId == compId);
    if (!tab) return state;
    let newCompany = compUpdate(tab.details);

    let compEdited = tab.edited || tab.details != newCompany;

    return {
        ...state,
        openCompanies: state.openCompanies.map((ct) => {
            if (ct.compId == compId) return {
                ...ct,
                edited: compEdited,
                details: newCompany
            };
            return ct;
        })
    };
}

We utilized this function in various reducer operations to update a single Company instance as demonstrated below:

case CompanyStateService.UPDATE_SHORTNAME: 
return applyCompanyEdit(state, action.payload.compId, (comp: CompanyFull) => {
    return {
        ...comp,
        shortName: action.payload.shortName
    }
});
case CompanyStateService.UPDATE_LONGNAME: 
return applyCompanyEdit(state, action.payload.compId, (comp: CompanyFull) => {
    return {
        ...comp,
        longName: action.payload.longName
    }
});

This approach proved effective for us by maintaining clean and comprehensible reducer operations, eliminating the need to repeatedly find and update objects. While our nesting was not overly deep in this scenario, the structure could be extended to handle more complex scenarios within the applyCompanyEdit function, thus containing the intricacy in one centralized location.

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

Exploring properties of nested elements in React

Picture a scenario where a specific element returns: <Component1> <Component2 name="It's my name"/> </Component1> Now, what I want to accomplish is something like this: <Component1 some_property={getComponent2'sN ...

What is the process for determining the time gap in minutes between two timestamps using Angular's reactive forms?

Seeking assistance with calculating the time difference between two timestamps (in minutes) using angular reactive forms. For example, inputting a start time of 11:50 and an end time of 12:50 should result in an output of 01:00 in the designated field. C ...

Elevate the placeholder in Angular Material 2 to enhance its height

I want to make material 2 input control larger by adjusting the height property of the input using CSS <input mdInput placeholder="Favorite food" class="search-grid-input"> .search-grid-input { height:30px!important; } As a result, the image o ...

Tips for preventing the exposure of internal components in Angular

Imagine an OutlookBarComponent that contains various bars similar to those seen in Microsoft Outlook. Each bar has a child component called OutlookBarItemComponent. Both the OutlookBarComponent and OutlookBarItemComponent are encapsulated within the Contro ...

Having trouble accessing the 'next' property of an undefined object in Angular. Issue arises when trying to rename

I have created an Angular service that utilizes sockets to send and receive data, then forwards the response to subscribers: import { Subject } from 'rxjs/Subject'; import { Injectable } from '@angular/core'; import * as io from &a ...

I am encountering an issue with my code where the function this.ProductDataService.getAllProducts is not recognized when

Encountering an issue while running unit test cases with jasmine-karma in Angular 7. The error received is: ProjectManagementComponent should use the ProjectList from the service TypeError: this.ProjectManagementService.getProject is not a function If I ...

Seeking assistance with printing an HTML template using a printer in Angular 2. Can anyone provide guidance on how

I have a scenario where I need to print an HTML template from within my Angular 2 component. Specifically, I want to be able to click on a print button on the same page and trigger the print function, which would display a print preview and allow the use ...

Extracting IDE autocomplete in a Vue.js component can greatly improve your workflow and efficiency

Currently, I am in the process of developing a component library for the organization where I work. To achieve this, I am utilizing vuetify in combination with vue.js. One of the tasks at hand is to create custom components such as company-autocomplete, w ...

Top method for dynamically loading a specific component by using its selector as a variable

I'm currently in the process of developing a straightforward game using Angular. The game is structured to consist of multiple rounds, each with unique characteristics that are distinguished by the variable roundType. For instance, round types can in ...

Is it possible for jQuery to operate on an HTML structure that is stored in memory

Exploring In-Memory HTML Structures with jQuery. Take a look at this snippet of HTML code: <div id="template" hidden="hidden"> <div class="col-md-3 margin-bottom20"> <p id="template-title" class="text-capitaliz ...

Expanding a Typescript class with a new method through its prototype

https://i.sstatic.net/3hIOo.png I'm encountering an issue while attempting to add a method to my Typescript class using prototype. Visual Studio is giving me a warning that the function does not exist in the target type. I came across some informati ...

How to extract a value from [object object] in Angular4

In my previous question, I shared the code below: getUserRole() { const headers = new Headers(); headers.append('Authorization', `Bearer ${this.getToken()}`); console.log(this.getToken()); const options = new RequestOptions({ headers: he ...

Specialized Character Formats in TypeScript

In my quest to enhance the clarity in distinguishing different types of strings within my program - such as absolute paths and relative paths, I am seeking a solution that ensures functions can only take or return specific types without errors. Consider t ...

How can Angular 2 route guard be set up to restrict access to routes based on specific URLs?

Is there a way to ensure that the route guard only allows routes from specific URLs? For instance, I would like '/url2' to be accessible only from '/url1' and not from any other URLs or by directly entering 'example.com/url2' ...

TypeScript: Defining a parent class type for its children

Dilemma I am seeking a way to structure a JSON object containing three different types of objects, all derived from the same parent object. Additionally, I wish to designate their specific types for improved Intellisense functionality within my codebase w ...

Encountered an error while trying to access a property in an array from a JSON

Why am I continuously receiving undefined error messages and how can I prevent them when attempting to read json data? Currently, I am delving into Angular and embroiled in a project that involves externally sourced json. Here's a snippet of the json ...

Transforming TimeZone Date Object without Altering Time

I am looking to change the time zone of a date from one timezone to another. For example: "Sun May 01 2019 00:00:00 GMT+0530 (India Standard Time)" is the current date object. I want to convert this date based on a specific timeZone offset, let's say ...

Ways to expand a TypeScript interface and make it complete

I'm striving to achieve the following: interface Partials { readonly start?: number; readonly end?: number; } interface NotPartials extends Partials /* integrate Unpartialing in some way */ { readonly somewhere: number; } In this case, NotPar ...

Passing properties from the parent component to the child component in Vue3JS using TypeScript

Today marks my inaugural experience with VueJS, as we delve into a class project utilizing TypeScript. The task at hand is to transfer the attributes of the tabsData variable from the parent component (the view) to the child (the view component). Allow me ...

A step-by-step guide on injecting dependencies manually in NestJS

When working with Angular, it is possible to access and inject dependencies manually using the built-in Injector class. This allows you to access and inject services without having to pass them through the constructor. Essentially, you can inject a service ...