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.