Effective methods for managing ngrx/redux action contexts

I've been searching for the best approach to handle this situation, but I haven't come across one yet.

issue: I want to avoid duplicating action files, such as in the examples home-todos.actions and sport-todos-actions. Instead, I aim to use a single to-dos.action file and a common reducer.

example: Let's say I'm developing a todo application where dispatching an action with type 'ADD_TODO_ASYNC' triggers actions in both the home and sport sections.

todos.actions.ts

const ADD_TODO_ASYNC = 'ADD TODO ASYNC';
const ADD_TODO_COMPLETE = 'ADD TODO COMPLETE';
const ADD_TODO_FAILD = 'AD TODO FAILD';

class addTodoComplete {
    type = ADD_TODO_COMPLETE;
}
class addTodoFaild {
    type = ADD_TODO_COMPLETE;
}

export type Actions = addTodoComplete | addTodoFaild;

sport.effects.ts

@Injectable()
export class SportTodosService {

    @Effect() ADD_TODO_ASYNC$ = this.actions$.ofType(TodosActionTypes.ADD_TODO_ASYNC)
    .map(toPayload)
    .swithMap( (todo: Todo) => this.api.addTodo(todo))
    .map((todo: Todo) => TodosActionTypes.addTodoComplete(todo))
    constructor(
        private actions$: Actions,
        private api: api
    ) { }

}

home.effects.ts

export class HomeTodosService {
    @Effect() ADD_TODO_ASYNC$ = this.actions$.ofType(TodosActionTypes.ADD_TODO_ASYNC)
        ...
    constructor(
        ...
    ) { }

}

reducer

function todosReducer(state, action: TodosActionTypes.Actions) {
    switch (action.type) {
        case TodosActionTypes.ADD_TODO_COMPLETE:
            return state;
        default:
            return state;
    }
}

app.module.ts

@NgModule({
    declarations: [
      AppComponent
    ],
    imports: [
      StoreModule.forRoot({
        sport: todosReducer,
        home: todosReducer,
      }),
      EffectsModule.forRoot([
        SportTodosService
        HomeTodosService,
      ])
    ],
    providers: [
        api
    ],
    bootstrap: [AppComponent]
  })
  export class AppModule { }

I'm trying to figure out the most optimal approach for this scenario. Should I prefix actions with context like 'HOME_ADD_TODO' & 'SPORT_ADD_TODO'?

Is there an official guideline to follow?

If you have a solution, whether it pertains to redux or ngrx, please share your insights.

Thank you!

Answer №1

If you truly want to grasp the complexity of this issue, it is crucial to reassess your application architecture. The notion of reusable reducers and actions may seem enticing at first glance, as it reduces redundancy and promotes the "Don't Repeat Yourself" principle.

However, a deeper examination reveals the potential dangers that lie ahead. While using a single 'ADD_TO_DO' action for both home and sports sections appears efficient now, it could spell trouble in the future. Imagine if your boss or customers demand a different functionality for adding to-dos related to sports - modifying the generic reducer could break your entire app. Although you could resort to applying patches with conditional statements initially, this will only make your codebase less adaptable, readable, and maintainable as your application scales up.

Therefore, splitting the reducers into specific ones for each section along with separate action files becomes imperative. While this approach might seem redundant at present, it offers significant advantages and flexibility for future enhancements.

Best of luck navigating through these challenges!

Answer №2

Check out this link for some useful patterns and techniques for ngrx.

The functionality is designed to operate as you have detailed it. The this.actions$ Observable will emit wherever it is utilized. Since TodosActionTypes.ADD_TODO_ASYNC is the same type in both home.effects.ts and sport.effects.ts, it will be emitted in both contexts.

It may not be possible to avoid having separate actions in your scenario, but you can reduce the amount of repetitive code written.

You could consider implementing something along these lines:

todos.actions.ts

abstract class addTodoComplete{
   constructor(readonly type: string){
      //rest of the behavior
   }
}
abstract class addTodoFailed{
   constructor(readonly type: string){
     //rest of the behavior
   }
}

todos.sport-actions.ts

const ADD_TODO = "[Sport] Add Todo";
const ADD_TODO_FAILED = "[Sport] Add Todo Failed";
class sportsAddTodoComplete extends addTodoComplete{
   constructor(){
      super(ADD_TODO);
      //rest of the behavior
   }
}
class sportsAddTodoFailed extends addTodoFailed{
   constructor(){
     super(ADD_TODO_FAILED);
      //rest of the behavior
   }
}

A similar approach would apply to the home version.

In addition, it's likely that you will need distinct SportTodosActionTypes and HomeTodosActionTypes.

This method won't completely eliminate "copy-paste" tasks, but it should help streamline the process to a certain extent.

UPDATE:

Regarding reducers, while you will indeed have to create two reducers using this method, it doesn't necessarily mean duplicating all the work.

sport.reducer.ts

import { todoReducer } from './reducer';

export function sportsTodoReducer(state, action: SportTodoActionTypes.Actions){
   todoReducer(state, action);
}

Similar logic can be applied to the home version.

Answer №3

To solve this issue, consider utilizing action Namespaces

When dealing with multiple actions in an application that pertain to different parts of the store, it's essential to use Action Constants as unique identifiers. By implementing Action Namespaces, we can prevent duplicate action logical failures. Here's how it works:

// todos.actions.ts
export const ADD_TODO = '[Home] Add Todo';

By including a namespace in the Action Constant that aligns with the specific store slice being used, such as the name of the feature module, we can easily identify the context of each action. This is especially helpful when debugging the application by logging actions. For example, switching from "Home" to "Sport" views would result in output like this:

[Home] Add Todo
[Home] Add Todo Success
[Sport] Add Todo
[Sport] Add Todo Success

For more information, refer to the SOURCE.

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

The content within the buttons is not displaying on the screen like it should

Having trouble with innerHTML in my code. I am populating an empty array called buttons using a for loop, and then trying to set each button's innerHTML to "Info" using another for loop. However, when I check my website, the buttons remain empty. //Ar ...

What methods can I use to ensure that a user's credentials are not shown in the URL?

My NextJS application sometimes behaves unexpectedly. Whenever I have a slow connection and the initial load time of the site is longer than usual, after trying to log in to the application, the credentials I entered are displayed in the URL. This happens ...

Warning: Shadcn-UI Form Alert - An element is converting an uncontrolled input to controlled mode

Throughout the course of this project, I found myself repeatedly using const [fileNames, setFileNames] = useState<string[]>([]); and logging the state with console.log(fileNames). However, every time I clicked on the parent element, an empty Array e ...

Enhance Data3 Sankey to disperse data efficiently

There are a few instances where the D3 Sankey spread feature is showcased here. However, it seems that this specific function is not included in the official D3 Sankey plugin. Is there anyone who can assist me in obtaining the code for the Spread function ...

Using JQuery to create a button inside a Modal dialog box

My goal is to select a row within a Table that is located inside a Modal window, and then have a button ('newBtn') within that Modal window trigger a post request to the server with the selected id of the row. However, the issue I am encountering ...

When velocity exceeds a certain threshold, collision detection may become unreliable

As I delve into detecting collisions between high-velocity balls, an obstacle arises. This issue seems to be quite common due to the nature of fast-moving objects colliding. I suspect that the solution lies within derivatives, and while I've drafted s ...

Issue with boolean data binding in Angular dialog button causing unresponsive behavior

My issue seems simple, but despite my efforts and searches online, I haven't been able to find a solution. In my Angular component, I have 2 dialogs and a button bound to a boolean in my typescript as shown below: export class ModalDB { constructor ...

Loading AJAX content using Selenium Webdriver as you scroll

Currently, I am utilizing Selenium WebDriver to retrieve the content from a website that unfortunately lacks an API. The site employs AJAX for dynamically loading content as the user scrolls through the page. In order to access this content, my approach in ...

The API is providing data, but it's being returned within an ambiguous object. What could be causing this confusion?

Utilizing https and async for simultaneous calls to retrieve two objects, then storing them in an array. The call is structured as follows: if (req.user.isPremium == false) { // Free user - Single report let website = req.body.website0; let builtWit ...

Trouble encountered while trying to dynamically update an array list in ReactJs

Currently, I am immersing myself in learning reactJS by working on a practical example. In this particular example, there is a form textfield that allows users to add an item to an existing array by clicking a button. However, I've encountered a few e ...

Revealing the name of the current state using UI router

Seeking a solution to implement a language switcher that seamlessly navigates users from the "en" side to the corresponding "de" page when they click on the language toggle. Currently, I am exploring the $state parameter and noticing that accessing the val ...

What is the correct way to end this jQuery statement?

I've been working on this for about 6 hours now. I ran it through multiple Lint tools and various other online tests, but I just can't seem to get the statement below to close properly. There's a persistent error showing up on the last line ...

What is the most efficient method for linking the hostname of my website to the file path within my Express.js static file server?

When using vanilla JavaScript in the browser, we can retrieve the hostname using: window.location.hostname However, if we are working with Node.js/Express.js on the server side, how can we achieve the same result? Furthermore, I am looking for a way to ...

Implementing a toggleable light and dark mode feature in Bootstrap 4 with a simple checkbox

Is it possible to link a checkbox's boolean value to a table's class in order to enable dark mode when checked? I attempted the following: <link href="https://stackpath.bootstrapcdn.com/bootstrap/4.2.1/css/bootstrap.min.css" rel="styleshee ...

Steps for inserting a video into a new div upon selecting a hyperlink

Looking for a way to link menu elements to corresponding videos in adjacent divs? I have two divs set up - one with the menu list and another right next to it where the videos should be loaded. Forms seem like a potential solution, but I lack expertise i ...

Encountered an error while attempting to load the object using the pre-signed URL in Min

I've been utilizing the Minio Server for managing files within my Flask API. I create Presigned URLs to facilitate direct image uploads from the Angular FrontEnd, reducing strain on Backend resources. While the Presign URL Generation functions correc ...

"Exploring the versatility of NextJS with dynamic route parameters

Can NextJS be configured to handle dynamic routes that match both /country and /country/city, while excluding matches like /country/city/whatever_content_here? The goal is to display the same content for either of the specified routes, regardless of whethe ...

Issue with Backbone view not initializing properly when navigating using the browser's back or forward buttons

My single page backbone application loads new pages via AJAX and updates the URL using the history API. However, when I press the back button, the URL changes but the previous AJAX content is not loaded. As I try to fix this issue, a strange error occurs ...

Different ways to display entries based on the level of authority

In my jHipster project, I have 4 entities: user, department, userAssignmentToDepartments (where a user may belong to several departments), and order. The questions I have are: How can I display only orders with a department_id that is included in the use ...

How to modify CSS style in SVG using Angular2?

I have been working on adding SVG to an Angular2 component's template and I've encountered some challenges. Here is the code snippet I am using: <object type="image/svg+xml" data="kiwi.svg" class="logo"> Kiwi Logo </object> To dyn ...