Advanced Typescript contains a parameter that specifies the type of callback function

Is it possible to create a function that is more typesafe than the current implementation?

public addBusinessRule(targetProperty: string,
                dependentProperties: string[],
                callback: (dep0: any, dep1: any, ...)): void {
   // some logic...
   callback(..dependentProperties)
};

My aim is to have typechecking for the last 2 arguments. For example:

this.addBusinessRule('mortage',
                     ['purchasePrice', 'investiture', 'ownFunds'],
                     (purchase: number, investiture: number, ownFunds: number) => {
    // some calculations
});

Does anyone have an idea on how I can ensure that the size of the string array in the second argument matches the number of arguments expected by the callback function?

Is there a way to potentially solve this using generics or is there simply no solution available?

If you have a suggestion for a more precise question title, please feel free to request an edit!

Answer №1

Utilizing mapped tuples allows for the transformation of property tuples into type tuples, which can then serve as arguments for a callback function. This ensures that the callback function has a maximum number of parameters equal to the items in the dependentProperties tuple. It does not mandate specifying all arguments (following TypeScript's definition of type compatibility for functions).

type Data = {
    mortgage: {
        purchasePrice: number,
        investiture: number,
        ownFunds: number,
        otherProp: string
    }
}
type MapTuple<T, K extends keyof T, NK extends Array<keyof T[K]>> = {
    [P in keyof NK]: NK[P] extends keyof T[K] ? T[K][NK[P]] : never
}
class Test {
    public addBusinessRule<K extends keyof Data, NK extends Array<keyof Data[K]>>(targetProperty: K,
        dependentProperties: NK | [], // Allow compiler to infer tuple types
        callback: (...a: MapTuple<Data, K, NK>) => void): void {
        // some logic...
        //callback(..dependentProperties)
    };
    public m() {
        this.addBusinessRule('mortgage',
            ['purchasePrice', 'investiture', 'ownFunds', 'otherProp'],
            (purchase, investiture, ownFunds, op) => { // parameter types inferred from Data type

            });
    }

}

The key element is the MapTuple type. This type leverages mapped types, now supporting tuples and arrays since version 3.1 (check out this PR). Each property in the tuple NK is matched with its corresponding type in T[K] using a conditional type due to an inherent TypeScript limitation.

If only parameter count verification is desired without enforcing specific types (although it could degrade API usability), consider the following approach:

type MapTuple<NK extends Array<any>> = {
    [P in keyof NK]: any
}
class Test {
    public addBusinessRule<NK extends Array<string>>(targetProperty: string,
        dependentProperties: NK | [], // Allow compiler to infer tuple types
        callback: (...a: MapTuple<NK>) => void): void {
        // some logic...
        //callback(..dependentProperties)
    };
    public m() {
        this.addBusinessRule('mortgage',
            ['purchasePrice', 'investiture', 'ownFunds', 'otherProp'],
            (purchase, investiture, ownFunds, op) => { // all parameters are of type any 

            });
    }
}

Answer №2

How about a more straightforward example, focusing solely on string arguments?

export const addBusinessRule = <T extends any[]>(targetProperty: string, dependentProperties: T, callback: (...a: T[]) => void) => {
  // some logic...
  callback(...dependentProperties)
}

addBusinessRule('asd', ['sdf', 'sdf', 'sdf'], (first, second, third) => {
  console.log('ABC')
})

This setup assigns types to each input.

If we attempt to use an incorrect callback function, TypeScript will show an error message.

const callback = (a: number, b: string, c: boolean) => {
  // ...
}

addBusinessRule('asd', ['sdf', 'sdf', 'sdf'], callback)
TS2345: Argument of type '(a: number, b: string, c: boolean) => void' is not assignable to parameter of type '(...a: string[][]) => void'. Types of parameters 'a' and 'a' are incompatible. Type 'string[]' is not assignable to type 'number'.

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

How to implement an Angular Animation that is customizable with an @Input() parameter?

Have you ever wondered if it's possible to integrate custom parameters into an Angular animation by passing them through a function, and then proceed to use the resulting animation in a component? To exemplify this concept, consider this demo where t ...

What is the best way to insert an item into a tree structure when determining the appropriate level for insertion is necessary beforehand?

Currently, I am dealing with a tree-like object structure: interface Node { id: number; name: string; children?: Node[]; } NODE_DATA: Node[] = [ { id: 1, name: 'A' }, { id: 2, name: 'B', children: [ ...

Using a function to reset a radio button selection in Ionic

Currently, I am in the process of developing an ionic app that includes a radio list on one of the pages. However, I have encountered an issue where the radio list does not clear when I navigate to the next screen and then return, resulting in a poor user ...

Is there a counterpart to ES6 "Sets" in TypeScript?

I am looking to extract all the distinct properties from an array of objects. This can be done efficiently in ES6 using the spread operator along with the Set object, as shown below: var arr = [ {foo:1, bar:2}, {foo:2, bar:3}, {foo:3, bar:3} ] const un ...

Ensuring Generics are Required in your Code

Can Generics be marked as mandatory in typescript? function validateGenerics<Result, Variables>({ foo, bar }: { foo: Result bar: Variables }) { console.log(foo, bar) } // Attempting to call the function without passing Gener ...

Obtaining the TemplateRef from any HTML Element in Angular 2

I am in need of dynamically loading a component into an HTML element that could be located anywhere inside the app component. My approach involves utilizing the TemplateRef as a parameter for the ViewContainerRef.createEmbeddedView(templateRef) method to ...

Steps for combining a sequence of subsequent subscriptions in RxJS

In my approach, I followed the code below. this.service1.info .subscribe(a => this.service2.getDetails(a.id) .subscribe(b => { this.doStuff(b); }) ); Lately, it has come to my attention that we will be adding more steps that gradu ...

Troubleshooting problem with Angular Click Outside Directive and unexpected extra click event issue

The challenge I'm facing involves implementing a custom Click Outside Directive for closing modal dialogs, notifications, popovers, and other 'popups' triggered by various actions. One specific issue is that when using the directive with pop ...

How to detect changes in Angular2 forms

Exploring Angular2 4.0, I've created a FormGroup structured as follows: this.form = this._fb.group({ a: ['', [Validators.required]], b: ['', [Validators.required]], c: ['', [Validators.required]], ...

Maximizing the efficiency of enums in a React TypeScript application

In my React application, I have a boolean called 'isValid' set like this: const isValid = response.headers.get('Content-Type')?.includes('application/json'); To enhance it, I would like to introduce some enums: export enum Re ...

Issue: The parameter "data" is not recognized as a valid Document. The input does not match the requirements of a typical JavaScript object

I encountered the following issue: Error: Argument "data" is not a valid Document. Input is not a plain JavaScript object. while attempting to update a document using firebase admin SDK. Below is the TypeScript snippet: var myDoc = new MyDoc(); myDo ...

What is the method in AngularJS2 for using TypeScript to inject dependencies into components?

I have been encountering different methods of injecting dependencies into my component and not all of them seem to be working for me. I am curious about the advantages and disadvantages, what the recommended best practices are, and why some methods are not ...

Is it possible to specify the version of a dependency using Stackblitz?

Is it possible to specify the dependency version on StackBlitz? I recently updated the dependency on NPM, however StackBlitz seems to be stuck on installing the old version. ...

Bringing together a collection of objects connected by shared array elements

Given the types defined as: type A = { commonKey: { a: string }[] }; type B = { commonKey: { b: number }[] }; Is it possible to create the type below without explicitly specifying commonKey? type C = { commonKey: { a: string, b: number }[] } My initial a ...

Looping through children components in a LitElement template

I aim to generate <slot>s for each child element. For instance, I have a menu and I intend to place each child inside a <div> with a item class. To achieve this, I have devised a small utility function for mapping the children: export functio ...

You are unable to link to <custom directive selector> because it is not recognized as a valid property of 'div'

I am currently working on a project in StackBlitz, and you can find the link here: https://stackblitz.com/edit/angular-fxfo3f?file=src/directives/smooth-height.directive.ts I encountered an issue: Error in src/components/parent/parent.component.html (2:6) ...

What is the best way to find out if an array index is within a certain distance of another index?

I'm currently developing a circular carousel feature. With an array of n items, where n is greater than 6 in my current scenario, I need to identify all items within the array that are either less than or equal to 3 positions away from a specific inde ...

Learn the technique for dynamically modifying Angular components within the main root component during runtime

I am looking to incorporate a dynamic form where the configuration button triggers the switch from app-login-form to login-config-form. app.component.html <div class="container p-5"> <app-login-header></app-login-header> <div cla ...

An error may occur when Typescript is instantiated with a varying subtype of constraint

I am encountering the "could be instantiated with a different subtype of constraint" error when trying to return a result that should match the expected type of a function. Despite removing irrelevant details, I'm struggling to pinpoint what exactly I ...

Filtering an array of objects based on another array of objects in Angular2 through the use of pipes

I'm having trouble understanding how to use the angular pipe to filter an array of objects based on another array of objects. Currently, I have a pipe that filters based on a single argument. I am working with two arrays, array1 and array2, both cont ...