"Exploring the differences between normalization structures and observable entities in ngrx

I'm currently grappling with the concept of "entity arrays" in my ngrx Store.

Let's say I have a collection of PlanDTO retrieved from my api server. Based on the research I've done, it seems necessary to set up a kind of "table" to store them:

export interface IPlan {
    id?: string;
    name?: string;
    ...
}

export interface IPlanRedux {
    byId: { [key: string]: IPlan };
    allIds: Array<string>;
}

When a 'LOAD_PLANS' action is dispatched, an effect is triggered:

@Injectable()
export class PlanEffects {
  constructor(
    private actions$: Actions,
    private store$: Store<IStore>,
    private planService: PlansService,
  ) { }

  @Effect({ dispatch: true })
  loadPlans$: Observable<Action> = this.actions$
    .ofType('LOAD_PLANS')
    .switchMap((action: Action) =>
      this.planService.list()
        .map((plans: Array<PlanDTO>) => {
          return { type: 'LOAD_PLANS_SUCCESS', payload: plans };
        })
        .catch((err: ApiError) => {
          return Observable.of({ type: 'LOAD_PLANS_FAILED', payload: { code: err.code, msg: err.message } });
        })
    );
}

If everything goes smoothly, another action, 'LOAD_PLANS_SUCCESS', is dispatched. The reducer for this action looks like this:

private static loadPlansSuccess(plansRdx: IPlanRedux, type, payload: Array<PlanDTO>) {
    const plans = payload;
    const newPlans = plans.filter(plan => !plansRdx.byId[plan.id]);

    const newPlanIds = newPlans.map(plan => plan.id);
    const newPlanEntities = plans.reduce((entities: { [id: string]: IPlan }, plan: IPlan) => {
        return Object.assign(entities, {
            [plan.id]: plan
        });
    },
    {});

    return {
        ids: [ ...plansRdx.allIds, ...newPlanIds ],
        byId: Object.assign({}, plansRdx.byId, newPlanEntities),
    };
}

Everything appears to be functioning correctly, but there are two key concepts that I need to grasp here:

  1. Internal table structure for storing redux data.
  2. Front-end components integration.

The main challenge I am facing is dealing with an Observable<IPlanRedux> instead of an Observable<IPlan> within my components. While internally managing the IPlanRedux table, I also want to indicate which plans have been removed and added.

I hope I've articulated my thoughts clearly.

EDIT

I attempted to use selectors:

export interface IPlanRedux {
    entities: { [key: string]: IPlan };
    ids: Array<string>;
}

export const getPlans = (planRdx: IPlanRedux) => planRdx.entities;

const getPlansState = (state: IStore) => state.plans;
export const getPlanEntities = createSelector(getPlansState, getPlans);

In my component:

this.plans$ = this.store$.select(fromRoot.getPlanEntities)
    .map(plan => {
    if (plan.id != null)

Answer №1

It's important to remember that "Rules & Concepts" are just guidelines and should not be followed blindly. Every application is unique, so there is no one-size-fits-all solution.

Just because someone suggests using a table-like structure doesn't mean it's the best choice for your specific issue. You are the expert on your data and requirements, so trust your judgment when making decisions.


In regards to your questions:

1. Internal table for storing redux structure

A redux or ngrx store is typically used with immutable objects for better performance. Managing your data in a flat structure makes it easier to handle mutations compared to nested structures. However, if you're not working with immutables, either option may work. It's a technical decision based on personal preference.

2. Front-end components

There are guidelines like "smart & dumb components" and "select statements vs. let functions," but there isn't a perfect solution. Regarding Observable<IPlanRedux>, it's not required everywhere. If you prefer, create a service class with select statements instead.


Some personal advice tailored to your situation:

If this project is for learning purposes: Experiment, break things, and learn as much as possible. Refactor as needed - setbacks are part of the learning process. NGRX and the redux pattern are powerful tools but can be challenging to grasp initially.

If this project is for a client or production application: Stick to what you know or be prepared for quality and time trade-offs, especially if it's your first experience with NGRX and Angular 2. Mastery comes with practice, so don't hesitate to seek help when needed.

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

Having trouble installing Angular 4 with npm?

After installing Angular, I encountered an error when trying to use the command line "ng -v". Please refer to the attached jpeg file. My node version is 6.10.3. Does anyone have a solution? ...

The issue arises when IonViewDidLoad fails to retrieve data from the service class in Ionic after the apk file has been generated

Creating a form where users can input various information, including their country code selected from dropdowns. Upon submission, the data is displayed successfully when running in a browser. However, after building the apk file, the country codes fail to ...

How to Initialize Multiple Root Components in Angular 4 Using Bootstrap

We are working on a JQuery application and have a requirement to integrate some Angular 4 modules. To achieve this, we are manually bootstrapping an Angular app. However, we are facing an issue where all the Angular components are loading simultaneously wh ...

Creating dynamic HTML elements using ngFor within AngularJS allows for increased flexibility and customization on the

I'm a newcomer to Angular 5 and I'm looking to retrieve HTML elements from a .ts file and dynamically add them to an HTML file using ngFor. I attempted to use [innerHtml] for this purpose, but it was not successful and returned [object HTMLSelect ...

When utilizing Typescript to develop a form, it is essential to ensure that the operand of a 'delete' operator is optional, as indicated by error code ts(279

Can someone help me understand why I am encountering this error? I am currently working on a form for users to submit their email address. export const register = createAsyncThunk< User, RegisterProps, { rejectValue: ValidationErrors; } > ...

Error in Typescript for the prop types of a stateless React component

When reviewing my project, I came across the following lines of code that are causing a Typescript error: export const MaskedField = asField(({ fieldState, fieldApi, ...props }) => { const {value} = fieldState; const {setValue, set ...

TypeScript and Angular: Harnessing the Power of Directive Dependency Injection

There are multiple approaches to creating Angular directives in TypeScript. One elegant method involves using a static factory function: module app { export class myDirective implements ng.IDirective { restrict: string = "E"; replace: ...

What is the best way to access a component's data within its method using Vue and Typescript?

Starting a Vue.js project with TypeScript and using single file components instead of class-styled ones has been my goal. However, I have encountered a recurring issue where I get error TS2339 when trying to reference my components' data using the "th ...

Simulated FTP Connection Setup and Error Event in Node.js using Typescript

In my Node-Typescript-Express application, I am utilizing the FTP library to connect and retrieve files from a designated location. As part of creating unit tests, I am focusing on mocking the library to ensure coverage for code handling Ready and Error ev ...

Utilize Angular 2 routes within your express application

I've recently developed a web application that has the server-side built on Node.js and the client-side on Angular. The file structure of the project is as follows: |_ api |_ client |_ config |_ models |_ package.json |_ server.js However, I'm ...

The function `find()` will not provide any data, it will only output `undefined`

I am trying to extract the `s_id` field from this JSON data: { "StatusCode": 0, "StatusMessage": "OK", "StatusDescription": [ { "s_id": "11E8C70C8A5D78888E6EFA163EBBBC1D", "s_serial": " ...

Transferring various sets of information into a single array

In an attempt to generate JSON data in an array for passing to another component, I followed a method outlined below. However, being relatively new to this field, my execution may not have been optimal as expected. employeeMoney: any[] = []; employeeId: an ...

What is the rationale behind allowing any type in TypeScript, even though it can make it more challenging to detect errors during compile time?

Why is it that all types are allowed in TypeScript? This can lead to potential bugs at runtime, as the use of type "any" makes it harder to detect errors during compilation. Example: const someValue: string = "Some string"; someValue.toExponentia ...

Trouble with using the date pipe in Angular specifically for the KHMER language

<span>{{ value | date: 'E, dd/MM/yyyy':undefined:languageCode }}</span> I am facing a challenge where I need to identify the specific locale code for the KHMER language used in Cambodia. Despite trying various cod ...

Learn how to incorporate a 'Select All' feature as the primary choice in DevExtreme SelectBox with Angular 15 through Html coding

We are looking to replicate the following basic HTML select in Angular 15 using a dropdown control from DevExpress known as dx-select-box <select ng-model="selectedVal" ng-change="selectedValueChanged()"> <option value=&q ...

ESLint detected a promise being returned in a function argument where a void return type was expected

I'm encountering a recurring error whenever I run my ESLint script on multiple routers in my server. The specific error message is as follows: error Promise returned in function argument where a void return was expected @typescript-eslint/no-misuse ...

Replacing child routes with an Angular wildcard route

I'm facing an issue with integrating a module named "host" into the app-routing.module. The problem I encountered is that the wildcard route takes precedence over loading the Host component, leading to the display of the PageNotFoundComponent instead. ...

Navigating away from an Ionic 2 app running in the browser and then returning: tips and tricks

Currently, I am setting up my oauth2 authentication in Ionic2. Through my research, I have discovered that the Cordova InAppBrowser plugin can be utilized to handle the process of navigating to the website and granting access to the App. However, I am st ...

When the next() function of bcrypt.hash() is called, it does not activate the save method in mongoose

Attempting to hash a password using the .pre() hook: import * as bcrypt from 'bcrypt'; // "bcrypt": "^1.0.2" (<any>mongoose).Promise = require('bluebird'); const user_schema = new Schema({ email: { type: String, required: tru ...

Introduce a specialized hierarchical data structure known as a nested Record type, which progressively ref

In my system, the permissions are defined as an array of strings: const stringVals = [ 'create:user', 'update:user', 'delete:user', 'create:document', 'update:document', 'delete:document&ap ...