Here’s my code for a TablePresenter
class that accepts an array of objects of the same structure and handles their sorting, filtering, and paging. In setting up a TablePresenter
for use, for each of the properties in the objects in the targeted array, a client method uses the addProp
method to add a TablePresenterPropConfig
to specify how to sort on that property.
At line 9, in addProp
, the code checker is telling me that
Property 'setCurrentOrder' is optional in type '{ propName: string; whatToSort: (x: any) => any; comparer: (a: any, b: any) => SortOrder; defaultOrder: SortOrder; currentOrder: SortOrder; ariaSort: AriaSort; propsToSort: string[]; isCurrentPrimary: boolean; setCurrentOrder?: (order: SortOrder) => void; }' but required in type 'TablePresenterPropConfig'.
Why isn’t it finding setCurrentOrder
in the copy of TablePresenterPropConfig that I’ve instantiated? Or, perhaps the relevant question is, how is it finding that it’s "optional", and how do I fix that? This is part of an Angular 15 application and I’m developing in Visual Studio Code, in case that matters.
export type SortOrder = -1 | 0 | 1;
export type AriaSort = 'descending' | 'none' | 'ascending';
export class TablePresenter {
props: string[] = new Array<string>();
propConfigs = {};
addProp(partial: Partial<TablePresenterPropConfig>) {
let propConfig: TablePresenterPropConfig = {...new TablePresenterPropConfig(), ...partial};
this.propConfigs[propConfig.propName] = propConfig;
this.props.push(propConfig.propName);
}
private sourceData: any[];
private filter: ((row: any) => boolean) = (x => true);
private filteredData: any[];
private _pageSize: number = 100;
public get pageSize(): number { return this._pageSize; };
public setPageSize(size: number): void {
this._pageSize = size;
this.setDataPage(0);
}
private _currentPageNumber: number = 0;
public get currentPageNumber(): number { return this._currentPageNumber; }
public setPageNumber(pageNumber: number): void {
this.setDataPage(pageNumber);
}
private _dataPage: any[];
public get dataPage(): any { return this._dataPage }
private set dataPage(data: any) { this._dataPage = data; }
setSourceData(data: any[]): void {
this.sourceData = data;
this.sort();
this.filteredData = this.sourceData.filter(this.filter);
this.setDataPage(0);
}
setFilter(filter: (row: any) => boolean) {
this.filter = filter;
this.filteredData = this.sourceData.filter(this.filter);
this.setDataPage(0);
}
setDataPage(pageNumber?: number) {
if (pageNumber) this._currentPageNumber = pageNumber;
let start = this._currentPageNumber * this._pageSize;
this.dataPage = this.filteredData.slice(start, start + this._pageSize);
}
sort(propName?: string) {
if (propName === undefined) {
let currentPrimaryKey = this.props.map(prop => this.propConfigs[prop]).find(config => config.isCurrentPrimary);
propName = currentPrimaryKey ? currentPrimaryKey.propName : this.propConfigs[this.props[0]].propName;
}
// Set ordering data across the configs.
for (let prop of this.props) {
let config = this.propConfigs[prop];
if (prop === propName) {
config.setCurrentOrder(config.isCurrentPrimary ? -config.currentOrder : config.defaultOrder);
if (config.currentOrder === 0) throw (`Non-sortable property ${prop} was set as the primary sort key`);
config.isCurrentPrimary = true;
}
else {
config.isCurrentPrimary = false;
config.setCurrentOrder(0);
}
}
// Do the sort if there's data.
if (this.sourceData) {
this.sourceData.sort((row1, row2) => {
// Do the sort.
for (let prop of this.propConfigs[propName].propsToSort) {
let config = this.propConfigs[prop];
let order = config.isCurrentPrimary ? config.currentOrder : config.defaultOrder;
if (order === 0) throw (`Non-sortable property ${prop} was given as a sort key for primary sort key ${propName}`);
let a = (config.currentOrder === 1) ? config.whatToSort(row1) : config.whatToSort(row2);
let b = (config.currentOrder === 1) ? config.whatToSort(row2) : config.whatToSort(row1);
let comparison = config.comparer(a, b);
if (comparison === -1 || comparison === 1) return comparison;
}
return 0;
});
}
}
public static basicComparer(a: any, b: any) {
if (a < b) return -1;
if (a > b) return 1;
return 0;
}
/*
createListBasedComparer is a comparison function factory. Its output is a comparison function that takes two
parameters that are members of a list and return -1 if the first one is earlier in the list, 1 if it's later,
and 0 if they're the same item.
The list is supplied to the factory as the array *list*. For example, if the value supplied for *list* is
['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], the comparer that the factory returns can be used
to sort an array of days of the week in Sun-through-Sat order.
If the parameter *allowNonMembers* is set to true, the generate comparer will accept parameters not on
the list, treating them as greater than all items in the list, so that in an ascending sort they will all
be sorted to the end. If *allowNonMembers is false (the default), then an exception is thrown when a
value that isn't on the list is supplied to the comparer.
TODO: add parameter *isCaseSensitive*, default false. If false, the comparer should check whether
the two values have a *toLowerCase* method and, if so, convert them to lower case before comparing.
*/
public static createListBasedComparer<T>(list: T[], allowNonMembers: boolean = false) {
let func = function(a: T, b: T) {
let aIndex = list.indexOf(a);
let bIndex = list.indexOf(b);
if (allowNonMembers) {
if (aIndex === -1 && bIndex === -1) return 0;
if (aIndex === -1) return 1;
if (bIndex === -1) return -1
}
else {
if (aIndex === -1 || bIndex === -1) throw("Attempted to sort an array containing nonmembers of the list");
}
if (aIndex < bIndex) return -1;
if (aIndex > bIndex) return 1;
return 0;
}
return func;
}
}
export class TablePresenterPropConfig{
static readonly ariaSorts: AriaSort[] = ['descending', 'none', 'ascending'];
propName: string;
whatToSort: ((x: any ) => any) = row => row;
comparer: ((a: any, b: any) => SortOrder) = TablePresenter.basicComparer;
defaultOrder: SortOrder = 1;
currentOrder: SortOrder = 0;
ariaSort: AriaSort = 'none';
propsToSort: string[] = null;
isCurrentPrimary: boolean = false;
setCurrentOrder(order: SortOrder) {
this.currentOrder = order;
this.ariaSort = TablePresenterPropConfig.ariaSorts[1 + order];
}
}
2
Answers
Thanks to Dimava. Final, functional result, and tightened up, with the partial class moved to the constructor:
does not create an
TablePresenterPropConfig
instance – it createObject
instance with its propertiesYou need
instead to override properties of TablePresenterPropConfig instance