Is it possible for me to choose a specific reducer to dispatch to in ngrx?

Just started exploring Redux and curious about the best way to implement multiple reducers. Specifically, I'm interested in how to direct actions to a specific reducer.

For example, if I have two reducers: one for users and one for posts.

User Reducer

export function userReducer(state: User, action: Action) {
  switch (action.type) {
    case SOME_ACTION:
      return state;
    default:
      return state || new User();
  }
}

Post Reducer

export function postReducer(state: Post, action: Action) {
  switch (action.type) {
    case SOME_OTHER_ACTION:
      return state;
    default:
      return state || new Post();
  }
}

Initialization

StoreModule.provideStore({user: userReducer, post: postReducer}),

I would like to be able to target a specific reducer like this:

const store = this._store.select<User>('user');
store.dispatch({type: SOME_ACTION, payload: {blah: 'blah'}})

and have only the userReducer respond.

Currently, when I dispatch an action, both userReducer and postReducer are triggered. Is this the standard behavior in Redux?

If not, is there a more efficient way to achieve this? It seems inefficient for all reducers to update on every dispatch.

Update

This question is prompted by the following section:

default:
  return state || new User();

It doesn't seem ideal to check for null state in the default case. I would prefer to do this instead:

default:
  return new User();

Currently, I am unable to implement this because if I dispatch SOME_ACTION, both postReducer.default and userReducer.SOME_ACTION are executed.

Furthermore, I can foresee potential debugging challenges if I mistakenly use the same string for actions in multiple reducers, resulting in both being triggered.

Answer №1

To put it simply , it is not possible to dispatch an action to only a specific subset of registered reducers. (At least not in a straightforward manner and likely not without modifying the @ngrx/store implementation itself...)

This is how redux operates: whenever an action is dispatched, the store invokes all reducer functions with the current state and action, expecting a new state in return. If a reducer should not respond to a certain action, it must return the unchanged state it received (usually through the default case). (I believe this is one of redux's main strengths as it enables reacting to the same action in multiple reducers)

To achieve the desired behavior, my suggestion is to keep state initialization and the default case separate:

export const initialState: User = new User();

export function userReducer(state: User = initialState, action: Action) {
    switch (action.type) {
        case SOME_ACTION:
            /* Modify the state and return a new state */
        default:
            return state;
    }
}

During initialization, all reducer functions are called with an undefined state and an init-action. Using a default parameter (initialState) ensures the state is initialized with new User(). This also allows setting initialState = undefined if immediate state setup is not required. (I have written about store initialization here, if you are interested).

Unfortunately, you can't do

default:
   return new User();

as it would reset each slice of the state to a default state (new User()), as you've observed.

You bring up a valid point about potential debugging nightmares if actions with identical names accidentally trigger multiple reducers.

You're right, accidental duplication of action names could lead to issues. One approach to mitigate this is shown in the ngrx-example-app:

export const SEARCH =           '[Book] Search';
export const SEARCH_COMPLETE =  '[Book] Search Complete';

export class SearchAction implements Action {
    readonly type = SEARCH;

    constructor(public payload: string) { }
}

export class SearchCompleteAction implements Action {
    readonly type = SEARCH_COMPLETE;

    constructor(public payload: Book[]) { }
}

export type Actions
  = SearchAction
  | SearchCompleteAction

Note the use of a prefix for each action string with the entity's name. This also facilitates payload type checking during action dispatches. If concerned about duplicate action names, consider using names like

const MY_ACTION = '[Entity] MY_ACTION_565ef1b6-2c78-47b0-9b61-499ba9da3dde'
(a random GUID) - the exact name is not critical. Dispatching an action would still be the same:

import * as book from '../actions/book';

this.store.dispatch(new book.SearchAction('hello'));

Unless your app has an extremely large team, the likelihood of duplicate action names is minimal. Some developers even check for duplicate names during app initialization, though specifics are not readily available.

Answer №2

Is it possible to choose a specific reducer to dispatch to in ngrx?

Not in ngrx, and this is intentional. Every action is dispatched to every reducer, and it is up to the individual reducers to decide whether to handle the action or not. Since the state in a reducer should be immutable, if an action is not handled, the unchanged state is simply returned.

Here is a suggested approach:

function myReducer(state = someInitialState, action) {
  case (action.type):
    'ACTION_1': 
      ...
    'ACTION_2': 
      ...

    default: 
      return state;
}

This way, if the state is undefined, it will default to someInitialState.

If an action is not handled, the same state will be returned.

It is important to note that this concept is fundamental to Redux, and I recommend exploring http://redux.js.org before delving into ngrx.

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

Encountering a CORS error in my Angular application while attempting to access a locally hosted Node Express API

I've been struggling with a CORS issue and can't seem to find a solution. My Node API application was built using Express, and the consumer is a simple Angular application. I've tried various solutions such as using CORS and including header ...

How to programmatically close a Bootstrap modal in a React-Redux application using jQuery

Hello everyone, I hope you're all doing well. I am currently working on a React application that utilizes Redux. I have run into an issue while trying to close a modal in Bootstrap programmatically. The versions I am using are Bootstrap 4 and jQuery 3 ...

Troubleshooting: JavaScript code not functioning properly with variable input instead of fixed value

I have encountered an issue with a JS function that I'm using. The function is shown below: // A simple array where we keep track of things that are filed. filed = []; function fileIt(thing) { // Dynamically call the file method of whatever ' ...

Implementing conditions in Angular2 router

Here are my current routes: const appRoutes: Routes = [ { path: 'alert/:id', component: AlertDetailComponent }, { path: 'alerts', component: AlertsComponent }, { path: 'dashboard', component: EriskDashboardComponent }, { pa ...

When sending data from Angular to the server, the request body appears to be blank

To transfer data from the client side (using Angular) to a Node.js server, I have set up a service in Angular. export class AddTaskService { constructor(private http: HttpClient) { } url = 'http://localhost:3000/tasks'; posttasks(task) { ...

Is there a method to improve type inference in vscode?

Recently, I created a component with a click handler that looks like this: onClick={(e) => { e.stopPropagation(); }} It seems clear to me, but then the Typescript compiler complains that it's not a valid signature for onClick, which actually a ...

tslint issues detected within a line of code in a function

I am a novice when it comes to tslint and typescript. Attempting to resolve the error: Unnecessary local variable - stackThird. Can someone guide me on how to rectify this issue? Despite research, I have not been successful in finding a solution. The err ...

Guide to implementing an "export default" with various types and values in a TypeScript module

There is a simple way to export multiple values as default: class Car {...} class Bus {...} export default { Car, Bus } You can also easily export a type as default export default interface Airplane {...} However, exporting multiple types as default i ...

"Exploring the best way to open a new tab in Angular from a component

I am working on a simple Angular application with two components. My goal is to open one component in a new tab without moving any buttons between the components. Here is an overview of my application setup: Within my AppComponent.html file, there is a b ...

Create a collection of values and assign it to a form control in Ionic 2

How can I set default values for ion-select with multiple choices using a reactive form in Angular? FormA:FormGroup; this.FormA = this.formBuilder.group({ toppings:['',validators.required] }); <form [formGroup]="FormA"> <i ...

How can you prevent the keys from being read-only when mapping onto a type?

Here's a common query: How can I change keys from readonly to writable when using a type that is Readonly? For example: type Foo = Readonly<{ foo: number bar: number }> type Bar = /* What's the method to duplicate the Foo type, but w ...

In a Next.js project, Typescript seems to be overlooking errors related to proptype definitions and function types

Greetings everyone! I am currently working on a project using TypeScript and have implemented various rules and elements. However, I am facing issues with type errors for functions and props. Essentially, when using any function, it is necessary to specify ...

Mapped types: Specify mandatory properties depending on whether an array of identical objects includes a specific string value

Can an object property be set to required or optional based on the presence of a specific string in an array within the same object? type Operator = "A" | "B" type SomeStruct = { operators: Operator[]; someProp: string; // this should be ...

Greetings, Angular2 application with TypeScript that showcases the beauty of the world

I've been working on my first angular2 program and noticed some deviations from the expected output. typings.json: { "ambientDependencies": { "es6-shim": "github:DefinitelyTyped/DefinitelyTyped/es6-shim/es6-shim.d.ts#7de6c3dd94feaeb21f20054b9f ...

Facing a blank page with no errors displayed during the HTML rendering process in Angular version 6

One of the most frustrating aspects of working with Angular is the lack of information provided when there is a render error in an HTML page. Instead of specifying which page the error is in, Angular defaults to the route page and provides no further detai ...

Move the creation of the HTML string to an HTML template file within ngx bootstrap popover

I have incorporated ngx bootstrap in my project through this link To display dynamic HTML content in the popover body, I am using a combination of ngx-bootstrap directives and Angular template syntax as shown below: <span *ngFor="let item of items;"&g ...

Exploring ASP.Net Core features: IApplicationBuilder.Map for routing, serving SPA, and managing static

I am exploring the use of Asp.Net Core 2.2 to host my Angular application and handle API requests (on /api). In my Startup.cs file, specifically in the Configure method, I have configured it as follows: app.Map("/home", config => { ...

"Implementing a Redux structure to enhance audio player functionality and effectively manage error

Imagine I am in the process of developing an audio player that includes a control panel for users to pause/play the currently selected track, along with the actual audio players. This involves actions such as pausing/playing the track, with the audio playe ...

Introducing a variety of services into the system

From my understanding, services must be provided and injected, meaning each service needs to be placed inside the constructor like this: constructor (private a: AService, private B: BService) {} In my scenario, I have multiple services that all follow th ...

What could be the reason for my provider loading the data twice?

Recently, I have been following a tutorial on building an Ionic app that displays information about National Parks. The data is stored locally and loaded by a Provider in my application. However, I noticed that the data is being loaded twice by the Provide ...