Tips for dynamically calling a property member of a function type in Typescript

How can I determine if a member is a function in TypeScript?

Is dynamic typing discouraged in TypeScript?

function checkIfFunction<T, K extends keyof T>(obj: T, propName: K) {

  if (typeof obj[propName] === "function") {
     
      obj[propName]();

     //// Error: This expression is not callable, Type unknown has no call signature
     
  }

}


let person = {
  name: "John Doe",
  age: 30,
  greet: () => {
    console.log("Hello!");
  }
};

checkIfFunction(person, "greet");

Answer №1

What you're seeing here is a behavior in TypeScript where control flow analysis does not narrow the type of a property when accessed using bracket notation (`obj[propName]`) instead of dot notation (`obj.xyz`). This is identified as an issue within TypeScript (refer to microsoft/TypeScript#10530) and has not been resolved due to performance concerns and safety considerations.

The typical workaround for this situation is to store the property value in a separate variable and then use control flow analysis to refine its type:

function call_IF_Function<T, K extends keyof T>(obj: T, propName: K) {
  const objPropName = obj[propName];
  if (typeof objPropName === "function") {
    // `objPropName` type narrowed from `T[K]` to `T[K] & Function`
    objPropName(); // valid function call
  }
}

As shown above, `objPropName` is effectively narrowed from `T[K]` to `T[K] & Function`, making it callable.


Note that this approach is not completely type safe because the type `Function` allows calling with any arguments or none at all. Some functions may require specific argument types:

call_IF_Function({ oops: (s: string) => s.toUpperCase() }, "oops");
// Appears valid but results in a runtime error since 's' is undefined

To enhance type safety, you can constrain `T` so that function-valued properties do not expect arguments by modifying the generic constraints as follows:

function call_IF_Function<
  T extends Record<K, (() => void) | string | number>,
  K extends keyof T
>(obj: T, propName: K) {
  const objPropName = obj[propName];
  if (typeof objPropName === "function") {
    // `objPropName` type once again narrowed to `T[K] & Function`
    objPropName();
  }
}

After applying the constraints above, you'll achieve more secure behavior:

call_IF_Function(x, "name"); // Executes without issues

call_IF_Function({ oops: (s: string) => s.toUpperCase() }, "oops"); // Error!
// -----------------------> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Type '(s: string) => string' is not assignable to type '() => void'

For further experimentation, you can access the code on the Playground.

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

Modify the name of the document

I have a piece of code that retrieves a file from the clipboard and I need to change its name if it is named image.png. Below is the code snippet where I attempt to achieve this: @HostListener('paste', ['$event']) onPaste(e: ClipboardE ...

Creating a versatile protractor framework to streamline multiple project implementations

There's a concept in the works for creating a 'protractor core' that will be utilized by various projects for UI testing. Currently, I have an Angular project called 'project1' with e2e tests (cucumber-protractor-typescript) that c ...

Issue with accessing storage in Ionic Storage (Angular)

Currently, I am attempting to utilize Ionic storage for the purpose of saving and loading an authentication token that is necessary for accessing the backend API in my application. However, I am encountering difficulties retrieving the value from storage. ...

Angular2 Navigation: Redirecting to a dynamically constructed route

To start, I need to automatically redirect to today's date as the default. Below is the routing configuration I currently have set up: import { CALENDAR_ROUTE } from './_methods/utils'; export const appRoutes: Routes = [ { path: Cal ...

A guide to utilizing ngFor in Angular 7 to loop through nested JSON and display it in a ul li

Looking to insert a nested JSON into an unordered list using ngFor loop in Angular. Here's the expected output format in HTML: home.component.html <div class="col-md-3" id="leftNavBar"> <ul *ngFor="let item of nestedjson"> <li c ...

Error: The post method in $setup is not defined in Vue Composition API

Dealing with a frustrating issue in my Vue application. One page is functioning perfectly fine, while the other is causing trouble by displaying this error: The first page loads a collection of wordpress posts (Blog.vue) without any issues, but the second ...

Exploring nested objects within an instance

I'm facing an issue with accessing a variable object within my main object. I am able to access 'start', 'end', and 'category' without any problem, but I am unsure how to access the variable Object in my Angular web app d ...

Utilize TypeScript functions within Angular applications

I have successfully created a TypeScript module and after compiling it, I am generating a JavaScript file. However, when I try to use this JavaScript file within my Angular project in the index.html, it loads but I cannot access its functionality from any ...

What are the best techniques for concentrating on a kendo maskedtextbox?

What is the correct way to set focus on the kendo-maskedtextbox in TypeScript after the view has initialized? The information provided in Telerik's example here is lacking in detail. ...

How to Use ngFor to Create a Link for the Last Item in an Array in Angular 7

I need help with adding a link to the last item in my menu items array. Currently, the menu items are generated from a component, but I'm unsure how to make the last item in the array a clickable link. ActionMenuItem.component.html <div *ngIf= ...

Error: Attempting to access the 'tokenType' property of an undefined object is not allowed

We encountered an error while attempting to embed a report using the Power BI Angular library. TypeError: Cannot read properties of undefined (reading 'tokenType') at isSaaSEmbedWithAADToken (reportEmbed?navContentPaneEnabled=false&uid=am ...

What steps are required to customize a pre-existing DevExtreme JQuery DataGrid that was initially built in a cshtml file using Typescript?

I'm currently developing a web application using DevExtreme JQuery. Within the frontend, I have set up a DataGrid in a cshtml file. With DevExtreme functionality, it's possible to include an Add Button to the DataGrid that triggers a popup for in ...

Why do ES6 classes fail to set properties when an overloaded function is called within the constructor of the parent class?

I encountered a puzzling scenario while coding that has left me perplexed. Here's the situation: I am extending a class from a library, which serves as the "Parent"-class. It allows its subclasses to override the init-method for custom initialization ...

The data type 'Event' cannot be assigned to the data type 'string' in this context

Recently diving into Angular, I came across a stumbling block while working through the hero tutorial. The error message that popped up was: Type 'Event' is not assignable to type 'string' You can see the error replicated here. ...

The conversion of an array to Ljava/lang/Object is not possible

I'm currently working on a project using NativeScript app with TypeScript where I am trying to pass an array of android.net.Uri to a function. However, when attempting to do so, I encounter an error mentioning that the 'create' property does ...

What is the best way to assign the selected attribute to my mat-option element?

I am encountering an issue with my mat-select and mat-option control. I am trying to apply the selected attribute to the first mat-option control without using binding on [(ngModel)] or [(value)]. The mat-option control is being generated by the *ngFor d ...

Tips for bypassing arrow functions when sending prop values to another component?

**Stateful ApplicatorType Component** class ApplicatorType extends Component { public state = { applicatorTypes: ['Carpenter', 'Painter', 'Plumber'], applicatorTypesSelected: [], } public render() { allotedTypes = ( &l ...

Issue with Angular MatSelect Losing Selected Value in Reactive Form Upon Submission

Working on an Angular project with a reactive form that features a <mat-select> for selecting cities. Although the dropdown functions properly in displaying and allowing city selection, there's a problem when attempting to submit the form: the s ...

Injecting a useFactory provider in Angular is a common practice

I manage a factory provider service that selects a service based on a flag. Everything works fine when I need a debug students service, but when I set the flag to false, the application throws an ERROR TypeError: serverService.fetchData is not a function. ...

user interface grid element in Materia

After writing this code, I encountered the following error: No overload matches this call. Overload 1 of 2, '(props: { component: ElementType<any>; } & SystemProps<Theme> & { children?: ReactNode; classes?: Partial<GridClasses>; .. ...