Retrieve a specific item from the ngrx/store

My Reducer implementation in my Angular 2 app is designed to store state items related to price offers for Financial Instruments, such as stocks and currencies.

This is the implementation of my Reducer:

export const offersStore = (state = new Array<Offer>(), action:Action) => {
switch(action.type){

    case "Insert":
        return [
            ...state, action.payload
        ];
    case "Update":
            return state.map(offer => {
                    if(offer.Instrument === action.payload.Instrument)
                    {
                        return Object.assign({}, action.payload);
                    }
                    else return offer;
            });
    case "Delete":
        return state.filter(offer => offer.Instrument !== action.payload )
    default:
        return state;
    }

}

Although I've managed to make Inserts, Updates, and Deletes work, it was quite challenging. Working with Redux has required me to shift my coding paradigm after years of doing things differently.

In my App, I have an Instrument Component/Page that displays all available information for a specific Instrument identified by its InstrumentId like "EUR/USD" stored in the payload.Instrument property.

However, I'm struggling to efficiently search for a specific instrument in the store and retrieve it. Additionally, I want the fetched instrument to be continuously updated whenever changes occur in the store due to websocket pushes from the server. Thus, I need to search the store for a particular instrument and return it as an Observable that will update the View Component based on new data pushed to the store.

How can I accomplish this task?

Answer №1

Whenever a reducer is called with an action, it returns the new state.

In the code example provided, the state consists of a list of instruments without any index. Therefore, to check if an instrument exists in the list, the entire list needs to be searched.

What if the state were represented as a dictionary and a separate list of indexes was maintained?

The state type would look like this:

export interface OfferState {
  ids: string[];
  entities: { [id: string]: IOffer };
};

Each time an action is executed in Redux, a new state is returned. This concept is crucial as it prevents direct mutation of the state. It is recommended to strictly enforce this when composing reducers, for example by combining multiple reducers using compose:

> export default compose(storeFreeze, combineReducers) ({   oether:
> otherReducer,   offers: offersReducer });

Mistakes can easily happen in Redux, but using storeFreeze will alert you to any attempt to mutate the state directly. The essence of Redux lies in actions changing state and creating a new state rather than modifying the existing one, allowing features like undo/redo functionality.

Adapting the provided example, the Offer's reducer could be implemented as follows:

export interface OfferState {
  ids: string[];
  entities: { [id: string]: IOffer };
};

export default function(state = initialState, action: Action): OfferState {
    switch(action.type){
        case OfferActions.INSERT:
        const offer : IOffer = action.payload;
        return {
            ids: [ ...state.ids, action.payload.Instrument ],
            entities: Object.assign({}, state.entities, { [action.payload.Instrument]: action.payload})
        };
        case OfferActions.UPDATE:
            return {
                ids: [...state.ids],
                entities: Object.assign({}, state.entities,  { [action.payload.Instrument]: action.payload})
            }

        default:
            return state;
    }
}

Note that changes are applied to a temporary state via object.assign (deep copy) before returning the new state.

The approach taken in the other answer regarding combining different reducers seemed unclear. However, in your reducers/index.ts file, defining types and functions should follow this structure:

export interface AppState {
  otherReducer: OtherReducer;
  offers: fromOffersReducer.OfferState;
}

export function getOfferState() {
  return (state$: Observable<AppState>) => state$
    .select(s => s.offers);
}

export function getOtherReducer() {
  return (state$ : Observable<AppState>) => state$
    .select(s => s.otherReducer)
}

Within the offerReducer and otherReducer functions, specific queries can be defined to access necessary data. Although these functions may not be linked currently, they will be connected later through the getReducerFunctions.

Examples of such functions include:

export function getOfferEntities() {
  return (state$: Observable<OfferState>) => state$
    .select(s => s.entities);
};

export function getOffer(id: string) {
  return (state$: Observable<OfferState>) => state$
    .select(s => s.entities[id]);
}

These functions remain inactive until they are applied to relevant data, such as the offersReducer created earlier. By combining the two, the following composite functions can be formed:

import offersReducer, * as fromOffersReducer from './OffersReducer';

 export function getOfferEntities() {
   return compose(fromOffersReducer.getOfferEntities(), getOfferState());
 }

  export function getOffer(instrument:string) {
   return compose(fromOffersReducer.getOffer(instrument), getOfferState());
 }

Answer №2

Let me try to explain a method for setting up an array of objects in a way that is easy to understand for everyone.

In the ngrx example app, they demonstrate how to store an array of objects and retrieve a specific row by its id or key. To achieve this, you can create an object that contains your object, with the object's id as the property key. This allows you to add properties to the "entities" Object using any string as the property name. For instance, if your state has a property called "entities", whose value is an empty object, you can then populate this object by creating properties based on your array elements.

export interface BooksState {
  entities: { [id: string]: Book };
};

Let's start by adding one row from the book objects array to the "entities" object like this.

reducerState.entities[book.id] = book;

This code snippet will set the book's id as a property on the "entities" object. If you were to inspect it in the console or debug tools, it might look similar to this after creation:

reducerState.entities.15: { id: 15, name: "To Kill a Mockingbird" }

The ngrx example app works with the entire array by adding it to the state using the reduce operator in JavaScript.

const newBookEntities 
              = newBooks.reduce(
                  (entities: { [id: string]: Book }, book: Book) => 
                           { 
                              return Object.assign(entities, { [book.id]: book }); 
                           }, {});

Special functions are created to access parts of this state, which can later be combined to obtain specific rows.

export function getBooksState() {
  return (state$: Observable<AppState>) => state$
    .select(s => s.books);
}


export function getBookEntities() {
  return (state$: Observable<BooksState>) => state$
   .select(s => s.entities);
};
export function getBooks(bookIds: string[]) {
  return (state$: Observable<BooksState>) => state$
              .let(getBookEntities())
              .map(entities => bookIds.map(id => entities[id]));
   }

These functions are consolidated in the index.ts barrel in the example:

import { compose } from '@ngrx/core/compose';


export function getBooks(bookIds: string[]) {
   return compose(fromBooks.getBooks(bookIds), getBooksState());
}

This function chains calls together from right to left, first accessing the reducer state with getBooksState and then calling fromBooks.getBooks with the desired book ids and previous state obtained through getBooksState, allowing mapping to select the required states.

In your component, you can use this function to fetch specific books:

myBooks$: Observable<Books>  =  this.store.let(getBooks(bookIds)));

The returned observable updates automatically whenever the store changes.

Additional: The "let" operator on the store object passes the current observable holding the store's state object into the function returned by the "getBooks" function.

For more insight, let's take a closer look at the composition of these functions.

export function getBooks(bookIds: string[]) {
   return compose(fromBooks.getBooks(bookIds), getBooksState());
}

The compose function combines two functions here - fromBooks.getBooks(bookIds) and getBooksState(). It enables passing a value to the rightmost function and propagating its result down the line. Consequently, we pass the result of getBooksState to fromBooks.getBooks and ultimately return that outcome.

Both functions receive an Observable. getBooksState takes the store state observable object as a parameter, maps/selects it to the relevant slice of the store, and returns the new mapped observable. This transformed observable is then passed to fromBooks.getBooks, which further filters out only the necessary entities. Finally, the resultant observable is what gets returned via the "store.let" call.

If you need further clarifications, feel free to ask questions so I can assist you better.

https://github.com/ngrx/example-app

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

Error in Vuetify 3.1.2 with Vue 3 and TypeScript: Unable to assign type 'boolean' to type 'never'

I am currently building a project using Vue 3 (version 3.2.45), Typescript (version 4.9.4), and Vuetify (version 3.1.2). When working with Vuetify components, I often encounter situations where I need to pass specific props for styling, positioning, or ma ...

A guide to troubleshooting the "Cannot resolve all parameters error" in Angular

Recently delved into the world of angular 2, and I've come across my first challenge. I'm trying to establish a service for retrieving data from a server but I keep encountering this particular error Error: Can't resolve all parameters fo ...

Moving the sidebar from the app component to a separate component in Angular results in a situation where the sidebar does not maintain full height when the page is scrolled down

Within my Angular application, I have implemented a sidebar as a separate component. Previously, the content of the sidebar was housed within the main app component's HTML page, and it functioned properly by taking up the full height of the page when ...

Angular Material's *matNoDataRow directive is malfunctioning

I am having an issue with using the *matNoDataRow directive in Angular Material. I have created a MatTable with filtering functionality, and when no data matches the filter, I want to display a specific text. However, the directive does not seem to be work ...

Can a Vue application be made type-safe without the need for transpilation?

Is it possible for Vue to be type-safe when used without transpilation (without a build step) as well? Can TypeScript or Flow be used to ensure type safety? ...

Using TypeScript to execute a function that generates middleware for an Express application

I've developed a middleware for validating incoming data, but I encountered an issue where a function that takes a Joi object as a parameter and returns the middleware is causing errors during build. Interestingly, everything works perfectly fine duri ...

Optimizing Angular: Configuring baseHref for assets during development with SSR

During production, we set the base href using the following command: ng build --base-href /app/ This configuration works smoothly as our assets are also accessible at /app/assets/, just as expected. However, I have been facing difficulties achieving the ...

What is the best way to import a TypeScript file in index.js?

I've recently developed an application using the react-express-starter template. I have a "server" directory where the backend is created by nodejs, containing an index.js file: const express = require('express'); const app = express(); c ...

Trouble with storing data in Angular Reactive Form

During my work on a project involving reactive forms, I encountered an issue with form submission. I had implemented a 'Submit' button that should submit the form upon clicking. Additionally, there was an anchor tag that, when clicked, added new ...

Experiencing difficulties with managing immutable state within ngrx framework

Hi there, I'm currently exploring ngrx and trying to implement immutable state management. However, I've run into some issues with getting it to work properly. Below is the reducer I am working with: https://stackblitz.com/edit/brewbrut?file=src ...

There seems to be a disconnect between the React Redux store

When attempting to connect my store to a React application, I encountered the following error: TypeError: state is undefined store/index.js (Creating Reducer function) import {createStore} from 'redux'; const counterReducer = (state:{counter:0} ...

Safari does not display disabled input fields correctly

I have designed a simple angular-material form with various inputs that are organized using angular flex-layout. The form displays correctly in all browsers except for Safari on iOS devices. The problem in Safari arises when loading a form that contains d ...

What is the method for sending a Post request using the "content type" value of "application/x-www-form-urlencoded"?

Need to send a request to the oAuth2 authentication server to obtain a token. The request works fine in postman but encountering issues when trying to make the same request from Angular 4. CORS configuration is correct as other requests are functioning p ...

Display clickable buttons beneath the Material-UI Autocomplete component

I want to place buttons ("Accept" and "Cancel") below the MUI Autocomplete element, and I am trying to achieve the following: Limit the Autocomplete popover height to display only 3 elements. To accomplish this, pass sx to ListboxProps Ensure that the b ...

Exploring deep levels of nested FormArrays with Angular 2 and FormBuilder

Currently diving into Angular 2, I am embarking on the challenge of constructing a nested form, validating it, and adding new objects Projects to object GroupProject. Here is a snippet from my TypeScript file: ngOnInit() { this.myForm = this. ...

Setting up domain or IP Address in Angular with Spring Boot: A step-by-step guide

I'm facing an issue with my Angular 11 application hosted in the public folder of a Spring project. The Spring project is running on port 8075, and when I access my application from localhost:8075, everything works perfectly fine. However, when I try ...

Issues encountered when using AngularJS2's post method to send data to an ASP.NET Core backend result

I recently delved into learning Angular2 and Asp.net core, but I encountered an issue when trying to post an object. Here is the relevant code snippet: Service.ts file: export class SubCategoryService { //private headers: Headers; constructor(private htt ...

Ways to bring in external javascript files in reactjs

I'm currently working on a form that requires the user to input their location. To achieve this, I have integrated the npm package react-geosuggest-plus. However, I want to avoid including <script src="https://maps.googleapis.com/maps/api/js?key=AI ...

Each time I attempt to update my profile on the web application, I receive this notification

Working on creating a web app using react, redux, and node for managing profile data. I have a function that controls both creation and editing of profiles. The creation works fine, but I encounter an error message when trying to edit. I've reviewed m ...

typescript mistakenly overlooked a potential undefined value in indexed records

In my code, I have defined an index-based type X. However, when using a non-existing key, TypeScript does not accurately infer the result type as ValueType | undefined. Is there a solution to correct this issue? type ValueType = { foobar:string; } t ...