Utilize the class or interface method's data type

In the context of a child component calling a callback provided by its parent, this situation is commonly seen in AngularJS.
As I am utilizing TypeScript, I aim to implement strong typing for the callback in the child component.

Here is the initial state without any strong typings:

class ParentComponentController {
  public parentMethod(param1: number, param2: string): boolean {
    return param1 > 0 && param2.length > 2;
  }
}

class ChildComponentController {
  constructor(private parentMethod: Function) {}

  public onCertainTrigger() {
    this.parentMethod(10, 'something');
  }
}

This is how I can achieve strong typing, but it appears messy:

declare type parentMethod = (param1: number, param2: string) => boolean;

interface ICommonApi {
  parentMethod: parentMethod;
}

class ParentComponentController implements ICommonApi {
  public parentMethod(param1: number, param2: string): boolean {
    return param1 > 0 && param2.length > 2;
  }
}

class ChildComponentController {
  constructor(private parentMethod: parentMethod) { }

  public onCertainTrigger() {
    this.parentMethod(10, 'something');
  }
}

In an ideal scenario, I envision this being simplified into a one-liner. Is this feasible?

class ParentComponentController   {
  public parentMethod(param1: number, param2: string): boolean {
    return param1 > 0 && param2.length > 2;
  }
}

class ChildComponentController {
  // This leads to an error
  constructor(private parentMethod: ParentComponentController.parentMethod) {}

  public onCertainTrigger() {
    this.parentMethod(10, 'something');
  }
}

Answer №1

Presenting the solution:

class MainController {
  public mainFunction(param1: number, param2: string): boolean {
    return param1 > 0 && param2.length > 2;
  }
}

class SubController {
  constructor(private mainFunction: typeof MainController.prototype.mainFunction) {}

  public performAction() {
    this.mainFunction(10, 'example');
  }
}

Answer №2

Here's a tip that may not be as concise, but will help you avoid the hassle of having to constantly update the parent method type:

let mainController: MainComponentController;

class SubComponentController {
    constructor(private parentFunc: typeof mainController.mainFunction) {}
}

Answer №3

If you've already defined a type called parentMethod, you can implement it like this:

declare type parentMethod = (param1: number, param2: string) => boolean;

class ParentComponentController {

  //this is now a field with the type of parentMethod 
  public parentMethod: parentMethod = (param1: number, param2: string) =>
    param1 > 0 && param2.length > 2;
}

class ChildComponentController {
  constructor(private parentMethod: parentMethod) { }

  public onCertainTrigger() {
    this.parentMethod(10, 'something');
  }
}

new ChildComponentController(new ParentComponentController().parentMethod)
  .onCertainTrigger();

You can find the code example in this jsfiddle link: https://jsfiddle.net/sayan751/ttmzzkg9/

Answer №4

If you need to create a type alias within a namespace, although it may not be ideal, you can still make it work:

class MainController {
  public mainFunc(param1: number, param2: string): boolean {
    return param1 > 0 && param2.length > 2;
  }
}
namespace MainController {
      export type mainFunc = (number, string) => boolean;
}

class SubController {
  // This will throw an error
  constructor(private mainFunc: MainController.mainFunc) {}

  public onEvent() {
    this.mainFunc(10, 'example');
  }
}

Remember that interfaces and type aliases are not available during run-time; they are purely for design purposes.

Answer №5

The fundamental objective of Generics and Generic Constraints is to define the connection between different types.

interface ISharedApi {
    sharedMethod(param1: number, param2: string): boolean;
}

class MainComponentController {
    public sharedMethod(param1: number, param2: string): boolean {
        return param1 > 0 && param2.length > 2;
    }
}

class SubComponentController<P extends ISharedApi> {
    constructor(private parent:P) { }

    public onSpecificEvent() {
        this.parent.sharedMethod(10, 'something');
    }
}

let m = new MainComponentController();
let s = new SubComponentController(m);

If the interface is basic:

class MainComponentController {
    public sharedMethod(param1: number, param2: string): boolean {
        return param1 > 0 && param2.length > 2;
    }
}

class SubComponentController<P extends { sharedMethod: (param1: number, param2: string) => boolean}> {
    constructor(private parent:P) { }

    public onSpecificEvent() {
        this.parent.sharedMethod(10, 'something');
    }
}

let m = new MainComponentController();
let s = new SubComponentController(m);

Answer №6

The recommended approach in the initial post is to use the 'messy' snippet. TypeScript best practices advocate for this method. It's important to note that parentMethod is a type, and enclosing it within a namespace like

ParentComponentController.parentMethod
doesn't serve any useful purpose.

This scenario aligns with the issue addressed in the manual under the Needless Namespacing section:

To emphasize why unnecessary namespacing should be avoided, namespaces are typically used for logical grouping of constructs and to prevent naming conflicts. Since the module file itself already acts as a logical group, and its top-level name is determined by the importing code, adding an extra layer of module for exported objects becomes redundant.

Despite technically being a form of namespace, designating a namespaced type like

ParentComponentController.parentMethod
goes against the principle of Occam's razor. This unnecessarily complicates the structure without offering any design advantages.

In addition to using I-prefixed interfaces, Hungarian notation employing T prefixes for custom types can be utilized. This practice is common in other object-oriented languages, particularly C# which TypeScript draws inspiration from. The official Coding guidelines document also mentions prefix usage specifically within TypeScript code, without restricting Hungarian notation elsewhere.

Hence, the preferred method remains consistent, potentially opting for TParentMethod instead of ambiguous parentMethod type for improved readability:

declare type TParentMethod = (param1: number, param2: string) => boolean;

interface ICommonApi {
  parentMethod: TParentMethod;
}

class ParentComponentController implements ICommonApi {
  public parentMethod(param1: number, param2: string): boolean { ... }
}

class ChildComponentController {
  constructor(private parentMethod: TParentMethod) { }
  ...
}

If a callback function type arises infrequently and does not necessitate a separate type definition, specifying the function signature explicitly is suitable:

class ChildComponentController {
  constructor(private parentMethod: (n: number, s: string) => boolean) { }
  ...
}

This approach offers sufficient redundancy to maintain code safety.

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

Traversing through nested states in ui-router without refreshing the parent state

In my current project, there are multiple organizations featured. Each organization page consists of two main sections: a header displaying organization information that should only load once, and a tabs zone for navigating different sections. I have imple ...

Is there a way to tally ng-required errors specifically for sets of radio buttons?

Currently, I am working on a form in AngularJS that includes groups of radio buttons. One of my goals is to provide users with an error count for the form. However, I have encountered a peculiar issue: After implementing this code to keep track of errors ...

AngularJS: Ensuring Controller Functions are Executed Post AJAX Call Completion via Service

Here's the code snippet I have in my service. this.loginUser = function(checkUser) { Parse.User.logIn(checkUser.username, checkUser.password, { success: function(user) { $rootScope.$apply(function (){ $rootScop ...

Creating HTML elements dynamically with attributes based on the `as` prop in React using TypeScript

Is there a way to dynamically create HTML elements such as b, a, h4, h3, or any element based on the as={""} prop in my function without using if guards? I have seen a similar question that involves styled components, but I am wondering if it can be done ...

The value returned by Cypress.env() is always null

Within my cypress.config.ts file, the configuration looks like this: import { defineConfig } from "cypress"; export default defineConfig({ pageLoadTimeout: 360000, defaultCommandTimeout: 60000, env: { EMAIL: "<a href="/cdn-cgi/ ...

Displaying data-table with only the values that are considered true

Right now, I am utilizing the AgReact table to exhibit data fetched from my endpoints. The data-table is functioning properly, however, it seems to be unable to display false values received from the endpoints on the table. Below are the snippets of my cod ...

The designated function name can be either "onButtonClick" or "onClickButton"

I am a Japanese developer working on web projects. Improving my English language skills is one of my goals. What would be the correct name for a function that indicates "when a button has been clicked"? Is it "onButtonClick"? Maybe "onClickButton"? Co ...

Change the variable value within the service simultaneously from various components

There is a service called DisplaysService that contains a variable called moneys. Whenever I make a purchase using the buy button on the buy component, I update the value of this variable in the service from the component. However, the updated value does n ...

Leveraging moment.format Function in Angular within an HTML Context

Is there a way to implement the moment.format method in HTML? Currently, I am utilizing the toLocaleDateString method to showcase an array of dates: <ng-template let-event> <div>{{event.date.toLocaleDateString(' ...

Azure App Service for Mobile and Web applications

Currently, I am in the initial stages of assessing and designing the potential architecture for a Mobile App (using Xamarin) and a Web App (most likely using Angular). The Azure App Service appears to be a suitable option for hosting these services. Howeve ...

Is there a possibility that typescript decorators' features will be polyfilled in browsers lacking ES5 support?

According to the typescript documentation, a warning is issued: WARNING  If your script target is lower than ES5, the Property Descriptor will be undefined. If the method decorator returns a value, it will act as the Property Descriptor for the method. ...

Angular encountered a 403 error while making an HTTP POST request

After successfully implementing angularJS validation in my form on localhost, I encountered a 403 Error (Forbidden) on line 72 of my angular.min.js file when trying to upload the code to my web server. I have tried numerous solutions but remain stuck on t ...

Error: Cannot locate 'import-resolver-typescript/lib' in jsconfig.json file

Issue: An error occurred stating that the file '/Users/nish7/Documents/Code/WebDev/HOS/frontend/node_modules/eslint-import-resolver-typescript/lib' could not be found. This error is present in the program because of the specified root file for c ...

Retrieve the name from the accordion that was clicked

Hey there, I have a simple accordion that is based on an API called "names". <div *ngFor="let item of showDirNames | async | filter: name; let i = index;"> <button class="accordion" (click)="toggleAccordian($event, i)&q ...

Exploring the JSON structure within MongoDB

I am looking to build a Json tree using data from 3 collections retrieved from MongoDB. For instance: Each area can have connections to other areas, spaces can be connected to areas and other spaces, and dashes are linked to spaces. What is the best app ...

Tips for enlarging the font size of a number as the number increases

Utilizing the react-countup library to animate counting up to a specific value. When a user clicks a button, the generated value is 9.57, and through react-counter, it visually increments from 1.00 to 9.57 over time. Here's the code snippet: const { ...

Getting the mssql output in Protractor using VSCode

I recently tried running the code below and it seems like the connection is established successfully. However, I'm unable to see any output. Is there a way to view the query result? I am still learning about Protractor, NodeJS, and MSSQL connections. ...

Get the Highchart image downloaded within your Phonegap mobile application

Our team is currently working on a mobile app with the combination of Phonegap + Ionic. We have integrated Highcharts into our app and now we are looking to add a feature that allows users to share the Highchart on platforms like Facebook, Whatsapp, and Tw ...

No pathways can be established within Core UI Angular

I've been attempting to use the router link attribute to redirect to a new page, but instead of landing on the expected page, I keep getting redirected to the dashboard. Below is an overview of how my project's structure looks: [![enter image de ...

Tips for locating all "a" tags within an angular $http request

(Angular) I am facing an issue with locating all "a" tags displayed within the data-ng-bind-html="post.content" in my Angular http request. Previously, I had a jQuery function that found all links, extracted the URL, and added an onclick function to open t ...