When is the appropriate time to provide arguments to the constructor of a TypeScript service?

I am grappling with the concept of when to pass arguments to a service's constructor versus passing them in the execution of the service.

For instance, I have a service that filters rows from an Excel sheet:

@Injectable()
export class FilterRowsService {
  private rows: AvailablesLogWithDate[];

  constructor(rows: AvailablesLogWithDate[]) {
    this.rows = rows;
  }

  execute({
    descriptions,
    products,
    warehouse,
    dates,
  }: {
    dates?: Dates;
    descriptions?: string[];
    products?: string[];
    warehouse?: string;
  } = {}) {
    this.filterByDates({ dates });
    this.filterByProducts({ products });
    this.filterByDescriptions({ descriptions });
    this.filterByWarehouse({ warehouse });

    return this.rows;
  }

  private filterByWarehouse({ warehouse }: { warehouse: string }) {
    if (!warehouse) {
      return this.rows;
    }

    this.rows = this.rows.filter((row) => {
      const warehouseInfo = warehousesInfos[warehouse];
      const warehouseKeywords = warehouseInfo.availableKeyWords;

      return warehouseKeywords.includes(row.Location);
    });
  }

I have various filtering methods that modify the state of the rows. I opted to pass the rows to the constructor and create a class variable so I wouldn't have to repeatedly pass the rows in each filtering method. However, I'm hesitant about altering the state of the rows directly. Perhaps it's acceptable since it only happens within the class.

Alternatively, the second approach would involve not having a class variable for rows, passing them to the execute method, and implementing something like this:

@Injectable()
export class FilterRowsService {
  execute({
    rows,
    descriptions,
    products,
    warehouse,
    dates,
  }: {
    rows: AvailablesLogWithDate[];
    dates?: Dates;
    descriptions?: string[];
    products?: string[];
    warehouse?: string;
  }) {
    let filteredRows = rows;
    filteredRows = this.filterByWarehouse({ warehouse, rows });
    filteredRows = this.filterByDates({ dates, rows });
    flteredRows = this.filterByProducts({ products, rows });
    filteredRows = this.filterByDescriptions({ descriptions, rows });

    return filteredRows;
  }

  private filterByWarehouse({
    warehouse,
    rows,
  }: {
    warehouse: string;
    rows: AvailablesLogWithDate[];
  }) {
    if (!warehouse) {
      return rows;
    }

    return rows.filter((row) => {
      const warehouseInfo = warehousesInfos[warehouse];
      const warehouseKeywords = warehouseInfo.availableKeyWords;

      return warehouseKeywords.includes(row.Location);
    });
  }

In this scenario, each filtering method produces a new filteredRows variable, which then gets updated after every filtering method in the execute function, ensuring the initial rows are never mutated.

The second approach appears more scalable and proper, but I'm curious if the first approach has any merit and, if not, why that is.

Answer №1

Uncertain about the connection to DDD, but regardless...

In most cases, passing arguments in the constructor is suitable when the dependencies have the same lifecycle as the object being created. On the other hand, method arguments are preferred when the dependencies are only necessary for a specific use case. Additionally, it is advised that services remain stateless for easier comprehension, reusability, and thread safety.

Although JavaScript doesn't place a strong emphasis on thread safety, it seems cumbersome for the client code to repeatedly instantiate a new service just to filter rows. Moreover, Angular services maintain singleton behavior within their injection context, making the constructor(rows) approach even less feasible.

I realize the desire to remove the need to pass around rows while keeping the service stateless.

  1. One option is to create a separate class to manage the filtering process. By abstracting this as an implementation detail, the service class can remain stateless.
class FilterRowsService {
    filter(rows, ...) {
        return new FilterProcess(rows, ...).execute();
    }
}
  1. Utilize closures to encapsulate rows. Although this may result in functions being recreated, the performance impact should be negligible in this scenario.
class FilterRowService {
    filter(rows, ...) {
        filterByWarehouse();
        filterByDates();
        
        return rows;

        function filterByWarehouse() {
            rows = rows.filter(...)
        }

        function filterByDates() {
            rows = rows.filter(...)
        }
    }
}
  1. Combine filters into a single unit or chain them together. By focusing solely on predicates, there's no need to pass rows around. This approach also minimizes iterating over rows multiple times by constructing a composite predicate encompassing all conditions. The warehouseFilter and datesFilter functions can serve as factories for (row) => boolean predicates.
class FilterRowsService {
    filter(rows, ...) {
        const compositePredicate = [warehouseFilter(warehouse), datesFilter(dates), ...]
            .reduce((a, b) => row => a(row) && b(row));
 
        return rows.filter(compositePredicate);
    }
}

Here's an example demonstrating this concept:

class SomeFilterService {
    filter(items, text, color) {
        return items.filter(this._composePredicates(
            this._textIncludes(text),
            this._colorIs(color)
        ));
    }
    
    _composePredicates(...predicates) {
        return predicates.reduce((a, b) => item => a(item) && b(item));
    }

    _textIncludes(text) {
        return item => item.text.includes(text);
    }
    
    _colorIs(color) {
        return item => item.color === color;
    }
}

const items = new SomeFilterService().filter([
  { text: 'hello', color: 'blue' }, 
  { text: 'test', color: 'blue' }, { text: 'test', color: 'red' 
 }], 'test', 'blue');

console.log(items);

If the service primarily focuses on composing predicates, consider employing the Builder pattern instead for filter composition.

This response aims to provide adequate guidance and alternatives for making informed design choices to address your issue.

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

Unable to call Success function in JQuery AJAX request

Here is a simple JQuery ajax request I am working on: $.ajax("../ajax/data/items.json", { success: setContent, type: "GET", dataType: "json" }); function setContent(data, status, jqxhr) { alert("Hello!"); } The json file loads successfully with a 200 r ...

What is the best way to establish a model using a date index?

I'm trying to access an API using Angular that returns an array with dates as indexes. After attempting to create a model, I encountered some issues. How should I modify it? This is what the API response looks like: { "Information": { " ...

Is there a way to refresh the page in NextJs whenever the parameters are modified without the need for reloading?

Currently, I am navigating on http://localhost:3000/?groupId=chicago&dayOfWeek=tuesday. Despite pressing a button on a component, the desired outcome of transitioning to another day, like Thursday, is not occurring. import { useRouter, usePathname, use ...

The use of history.pushState in Chrome triggers a request for the favicon

Code : var newurl = window.location.protocol + "//" + window.location.host + window.location.pathname +"?"+ queryStr; window.history.pushState({path:newurl},'',newurl) Current issue : There is a problem where each time wind ...

Attempting to Retrieve Information from a Get Request using Axios

I am attempting to retrieve SQL data from a GET request using axios. My backend is set up with an express server, and the front end is built with React. In my React component, I have a function that contains the axios GET call, which I then invoke within t ...

Utilizing the [mat-dialog-close] directive within an Angular dialog component

While attempting to utilize the suggested code in the dialog template for opening a dialog component to either confirm or cancel an action, I encountered an error with the following message. Why did this happen? Property mat-dialog-close is not provided by ...

Error occurs in ASP.NET AJAX Control Toolkit while uploading files within Scriptresource.axd

I recently encountered an issue with my AJAX Control Toolkit File Upload (Version 15.1.4) in my ASP.Net Web Application. Up until this week, it was functioning perfectly fine. However, starting yesterday, I started receiving a JavaScript error right after ...

Looking for regex to extract dynamic category items in node.js

Working on node.js with regex, I have accomplished the following tasks: Category 1.2 Category 1.3 and 1.4 Category 1.3 to 1.4 CATEGORY 1.3 The current regex is ((cat|Cat|CAT)(?:s\.|s|S|egory|EGORY|\.)?)( |\s)?((\w+)?([. ...

I am unable to refresh the table data in Angular

Here is the code that I am currently working with: I am facing an issue where my webpage is not updating its data after performing delete or any other operations. The user list is not being displayed in the data. Despite my attempts, I have been unable to ...

What is the best way to make a drop down menu list appear when I click on the parent li, and then show the sub li using JavaScript?

I have a code that includes an image, HTML, and CSS code. Can anyone please tell me how I can set up a drop-down menu list to open the sub <li> elements when I click on a parent <li> using JavaScript? You can see the appearance of the code in t ...

What is preventing me from using property null checking to narrow down types?

Why does TypeScript give an error when using property checking to narrow the type like this? function test2(value:{a:number}|{b:number}){ // `.a` underlined with: "Property a does not exist on type {b:number}" if(value.a != null){ ...

Can a function be passed as props in a scenario where both a Parent and Child component are functional components?

I have a component called ParentComponent where I am trying to pass a function named toggleDrawer to another component called ChildComponent in the following way: const ParentComponent = () { const [drawerState, setDrawerState] = useState(false); ...

What is the functionality of the remote data source in Jquery Mobile autocomplete feature?

Currently, I am browsing through this page and it appears that there is no clear documentation provided on the expected format or functionality of the remote data source. The example JavaScript code on the website references a remote data source at http:/ ...

Promise not being properly returned by io.emit

io.emit('runPython', FutureValue().then(function(value) { console.log(value); //returns 15692 return value; // socket sends: 42["runPython",{}] })); Despite seeing the value 15692 in the console, I am encountering an issue where the promise ...

Having difficulty sending emails with Nodemailer

This is a simple example showcasing the usage of Nodemailer library. var http = require('http'); var port = process.env.PORT || 8080; var async = require('async'); var nodemailer = require('nodemailer'); // Creating a transp ...

Converting (Geo)JSON data into an array of objects

I am new to JavaScript and facing some difficulties in converting GeoJSON to a JavaScript Object Array. Using JSON.parse, I successfully parse the JSON generated on the server into a JSON Object. The addGeoJson method from Google returns [object (Array)] ...

Jenkins encountered an issue where script execution was blocked on <URL> due to the document's frame being sandboxed without the 'allow-scripts' permission set

When using an iFrame in HTML, it's always best to remember to sandbox it and set the 'allow-scripts' permission to true. However, I'm facing an issue in my pure Angular JS application where there is no iFrame present. It runs smoothly ...

Discovering the ReturnType in Typescript when applied to functions within functions

Exploring the use of ReturnType to create a type based on return types of object's functions. Take a look at this example object: const sampleObject = { firstFunction: (): number => 1, secondFunction: (): string => 'a', }; The e ...

Re-establishing connections in the force-directed graph model

Just getting started with d3.js and I'm currently attempting to reconnect paths between nodes on a force graph. Here is an example image of what I am trying to achieve: https://i.sstatic.net/knvH9.jpg I want to be able to drag the red circle and hav ...

Vuejs Error: "No template or render function defined for a single file component"

When attempting to import all components from a folder and display one based on a passed prop, I encountered an error at runtime. I am using webpack with vue-loader to import all my components, each of which is a *.vue file. The issue arises when importi ...