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

Having difficulty accessing $scope beyond the function

One of the functions in my code is called getMail() $scope.getMail=function(){ $http.get(APISource). success(function(data) { $scope.mail =data; }). error(function(data) { console.log("error"); console.log(data); }); } In the succe ...

Using private members to create getter and setter in TypeScript

Recently, I developed a unique auto getter and setter in JavaScript which you can view here. However, I am currently unsure of how to implement this functionality in TypeScript. I am interested in creating an Object Oriented version of this feature if it ...

Declaration of dependencies for NestJS must include dependencies of dependencies

I'm encountering an issue where NestJS is not automatically resolving dependencies-of-dependencies: I have created an AWSModule which is a simple link to Amazon AWS with only a dependency on the ConfigService: @Module({ imports: [ConfigModule], ...

Crack encrypted information using Typescript after it was encoded in Python

I've encountered an issue while attempting to decrypt previously encrypted data in Python. Here is how I encrypt the data in Python: iv = Random.new().read( AES.block_size ) cipher = AES.new(secret_key, AES.MODE_CBC, iv) encrypdata = base64.b64enco ...

Can a reducer be molded in ngrx without utilizing the createReducer function?

While analyzing an existing codebase, I came across a reducer function called reviewReducer that was created without using the syntax of the createReducer function. The reviewReducer function in the code snippet below behaves like a typical reducer - it t ...

Query params in the component router of Angular 1

Currently, I am working with Angular 1 and the new component router. The $routeConfig that I have set up looks like this: { path: '/list', name: 'ListCmp', component: 'listCmp', useAsDefault: true } I am trying to navigate ...

An issue occurred in React 16.14.0 where the error "ReferenceError: exports is not defined" was not properly

As the creator of the next-translate library, I have encountered a perplexing issue with an experimental version involving an error specifically related to React 16.14.0. Interestingly, upgrading React to version 17 resolves the issue, but I am hesitant to ...

How can we transform the `toUSD(amount)` function into a prototype function?

This function is functioning perfectly as intended. function toUSD(amount): string { // CONVERT number to $0.00 format return new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }).format(amount); }; Here is how I currently i ...

Manually assigning a value to a model in Angular for data-binding

Currently utilizing angular.js 1.4 and I have a data-binding input setup as follows: <input ng-model="name"> Is there a way to manually change the value without physically entering text into the input field? Perhaps by accessing the angular object, ...

The Angular Animation constantly resets with each new action taken

In my Angular project, I am working on a scaling animation for a list. I want the animation to only trigger when specific buttons (red and green) are pressed. Currently, the animation restarts regardless of what I click on. Can anyone help me troubleshoot ...

Create a custom validation function that accepts additional parameters

At the moment, I have implemented the following code but I haven't utilized the extra data yet. create-room.component.ts import { Component, Inject, OnInit } from '@angular/core'; import { AbstractControl, FormBuilder, FormControl, FormGroup ...

Error: An unexpected character (.) was encountered | Building with npm has failed

When executing "npm run build", I encounter an error with the unexpected token (.) related to object values. Can someone assist me in resolving this issue? I am using tsc build for a react npm library. It seems like there might be a configuration problem ...

AngularJS uses double curly braces, also known as Mustache notation, to display

I'm currently working on a project where I need to display an unordered list populated from a JSON endpoint. Although I am able to fetch the dictionary correctly from the endpoint, I seem to be facing issues with mustache notation as it's renderi ...

Error: Disappearing textarea textContent in HTML/TS occurs when creating a new textarea or clicking a button

I've encountered an issue with my HTML page that consists of several textareas. I have a function in place to dynamically add additional textareas using document.getElementById("textAreas").innerHTML += '<textarea class="textArea"></text ...

How to pass an array as parameters in an Angular HTTP GET request to an API

Hey there! I'm relatively new to Angular and I've hit a roadblock. I need to send an array as parameters to a backend API, which specifically expects an array of strings. const params = new HttpParams(); const depKey = ['deploymentInprogre ...

Disable the submit form in AngularJS when the start date and end date are not valid

I want to ensure that my form submission only occurs if the validation for Start date-End date passes. In my form setup, I have the following elements... <form name="scheduleForm" id="scheduleForm" class="form-vertical" novalidate> <input t ...

How can I clear a text box in Angular.js when the Google Autocomplete event is triggered?

Seeking assistance in AngularJS to clear the textbox value upon triggering the google map autocomplete event. Can anyone provide guidance? Here is the HTML code - <input ng-model="deployText" id="search_execute" class="form-control" type="text" place ...

Enhance Graphql Queries with TypeOrm using Dynamic Filters

I am currently working on building a graphQL query system using Apollo, typescript, and TypeOrm. At the moment, I only have one table called users in my database. I am creating a getUsers GraphQL query which retrieves all users from the table. With the hel ...

Tips for integrating Typescript into a pre-existing Vue 3 project

The contents of my package.json file are as follows: { "name": "view", "version": "0.1.0", "private": true, "scripts": { "serve": "vue-cli-service serve" ...

Leverage Angular's template functionality

When working with Angular, you have the ability to inject the $compile service and dynamically compile elements. For example: let compiledElement = $compile('<my-elem></my-elem>')(myScope); But what I'm curious about is whether ...