Typescript: Understanding the Different Uses of 'this' and Implementing Generic Functions

Imagine creating an interface called addable, which defines classes with a method __add__ that adds objects of the same class together.

Additionally, consider having a standalone function called add that takes two addable objects of the same class and returns their sum.

The initial approach involved using the this type in the interface:

interface addable<T> {
    __add__: (this: T, other: T) => T;
}

An attempt at implementing this was made with a class named Point:

class Point implements addable {
    constructor(public x: number, public y: number) { }
    __add__(other: Point) {
        return new Point(this.x + other.x, this.y + other.y);
    }
}

However, this implementation failed with:

Class 'Point' incorrectly implements interface 'addable'.

Types of property 'add' are incompatible. Type '(other: Point) => Point' is not assignable to type '(other: this) => this'.

Type 'Point' is not assignable to type 'this'.

Further investigation revealed that the this type does not refer to the class itself, leading to the failure of this approach.

To resolve this issue, generics were introduced with a pseudo-parameter this:

interface addable<T> {
    __add__: (this: T, other: T) => T;
}

In this case, defining the class as specified worked. However, when attempting to define the standalone function add:

const add = <T>(a1: addable<T>, a2: addable<T>) => a1.__add__(a2);

The error received was:

The 'this' context of type 'addable' is not assignable to method's 'this' of type 'T'.

A potential solution proposed utilizing the following interface:

interface Addable<T> {
    add: (this: Addable<T>, other: Addable<T>) => Addable<T>;
}

This definition seemed promising. The standalone function would then be:

const add = <T>(a1: Addable<T>, a2: Addable<T>) => a1.add(a2)

Although effective, it was noted that this definition was too lenient and allowed any instances of Addable, not strictly those belonging to the same class.

The issue persisted even with different classes implementing the interface:

class Point implements Addable<Point> {
    constructor(public x: number, public y: number) { }
    add(other: Point) {
        return new Point(this.x + other.x, this.y + other.y);
    }
}

class Num implements Addable<Num> {
    constructor(public number: number) { }
    add(other: Num) {
        return new Num(this.number + other.number);
    }
}

const add = <T>(a1: Addable<T>, a2: Addable<T>) => a1.add(a2)

var p1 = new Point(1, 1);
var num1 = new Num(10);

const wrongAdded = p1.add(num1);  // correctly fails

const wrongAdded2 = add(p1, num1);  // typechecks! why???

The inferred type of add appeared to be <{}>, indicating a discrepancy that requires further understanding of TypeScript.

Answer №1

Is this achieving the desired outcome... I made the decision to completely remove the parameter this. If it's necessary, I would have given it a different name. This allows for one point to be added to another and returning the resulting point...

interface Summable<T> {
    sum: (other: T) => T;
}

class Coordinate implements Summable<Coordinate> {
    constructor(public xValue: number, public yValue: number) { }

    sum(other: Coordinate) {
        return new Coordinate(this.xValue + other.xValue, this.yValue + other.yValue);
    }
}

var positionOne = new Coordinate(1, 1);
var positionTwo = new Coordinate(3, 3);

var result = positionOne.sum(positionTwo);

alert(result.xValue + ' ' + result.yValue);

Note: I modified some names here to align them more closely with TypeScript as commonly used - to prevent other developers from blindly adopting your naming conventions in a live environment. However, feel free to use the names summable and __sum__ if you prefer :)

Answer №2

The previous iteration of the question came close to the solution. However, I mistakenly used incorrect syntax for the constraint on the generic type. The revised version is as follows:

interface Addable<T> {
    add: (this: Addable<T>, other: Addable<T>) => Addable<T>;
}
class Point implements Addable<Point> {
    constructor(public x: number, public y: number) { }
    add(other: Point) {
        return new Point(this.x + other.x, this.y + other.y);
    }
}

class Num implements Addable<Num> {
    constructor(public number: number) { }
    add(other: Num) {
        return new Num(this.number + other.number);
    }
}
const add: <T extends Addable<T>>(a1: T, a2: T) => T = function (a1, a2) {
    return a1.add(a2);
}

var p1 = new Point(1, 1);
var p2 = new Point(2, 2);
var num1 = new Num(10);

const added = p1.add(p2);
const added2 = add(p1, p2);

const wrongAdded = p1.add(num1);  // This operation will fail

const wrongAdded2 = add(p1, num1);  // This operation will also fail

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

Encountering difficulties: Payload CMS is unable to establish connection with mongo DB

Struggling to set up Payload CMS. When running npm run dev, I encountered an error stating: ERROR (payload): Error: cannot connect to MongoDB. Details: URI does not have hostname, domain name and tld [nodemon] app crashed - waiting for file changes befor ...

What is the best way to set up my page to detect the "enter" key input when the form is not created with the <form> tag?

Within the HTML code, data is received and stored in variables within my TypeScript file. At the end of the HTML, there is a button that was created separately from any user input containers. This button triggers a function that processes the information i ...

"Utilizing Primeng's dynamic functionality to create a selected p-tab

Utilizing the TabView module from primeng, I have created a dynamic tab where only the last tab remains static. The property used is 'selected', and for the dynamic tab, it is set as [selected]="'tab' + $index", where $index represents ...

TypeScript throws an error if trying to access an Object variable using a String

While the code below is functioning as intended, I am encountering an error in the VS Code Typescript compiler stating that "Type 'String' cannot be used as an index type". Oddly enough, using a string literal instead of a variable like ...

Using TypeScript will result in errors when attempting to use the Promise object and the Awaited keyword

In this example, I am trying to ensure that the function foo does not accept a Promise as an argument, but any other type should be acceptable. export {} function foo<T>(arg: T extends Promise<unknown> ? never : T) { console.log(arg); } asy ...

Unable to transfer data to other components via service

Two components were developed, one to display a list of all loans and the other to view detailed information about each loan upon clicking on a card in the list. Despite being able to see the data console logged within the subscribe function, no data is ap ...

Unable to execute a function within the same class in TypeScript

My experience with TypeScript is still fresh, and I've encountered an issue while trying to call a function within the same class. Here's the function in question: createPost(url: String, data: any) { $.ajax({ url: url + "/" + GLOBAL.R ...

How can I handle the different data type returned by ReactDom.render?

My main focus is on rendering Markdown. Additionally, I also need to parse HTML which is passed as a string. In this scenario, children represents the HTML passed as a string, while isParseRequired indicates if parsing is needed. import cx from 'clas ...

Dragging element position updated

After implementing a ngFor loop in my component to render multiple CdkDrag elements from an array, I encountered the issue of their positions updating when deleting one element and splicing the array. Is there a way to prevent this unwanted position update ...

What is the proper way to implement Firebase getDoc in Vue with vue-concurrency?

I am currently in the process of developing a TypeScript Vue composable that utilizes Firebase and the vue-concurrency library. According to the documentation, I need to explicitly type any intermediate values that are yielded. I am facing a challenge wh ...

Unveiling the mysteries of abstract classes in TypeScript

I have a collection of different animal classes, all derived from a common abstract base class. To illustrate: abstract class Animal { abstract speak(): string; } class Dog extends Animal { speak(): string { return "woof... sigh" } } ...

Angular 8: Implementing Form Validation with a Boolean Flag

Within my HTML code, I have a function (change)="limitUser($event)". In Typescript, I utilize a for loop to iterate through each element and determine if the value is less than 10. If it exceeds 10, the inValid = true condition is set. All form fields in m ...

How can I establish default values for 2 to 3 options in a Dropdownlist?

Is there a way to set two values as default in a dropdown list, and when the page is refreshed, the last two selected values are retained as defaults? Thanks in advance! Visit this link for more information ...

What is the approach to constructing an observable that triggers numerous observables depending on the preceding outcome?

One of my endpoints returns { ids: [1, 2, 3, 45] }, while the other endpoint provides values for a given id like { id: 3, value: 30, active: true }. I am currently attempting to create an observable that will call the first endpoint and then, for each id r ...

Guide to implementing the patchValues() method in conjunction with the <mat-form-field> within the (keyup.enter) event binding

I am currently working on a feature that populates the city based on a zip code input. I have successfully achieved this functionality using normal HTML tags with the (keyup) event binding. However, when trying to implement it using CSS, I had to use (keyu ...

Understanding React and TypeScript Higher Order Components: How to Resolve the Type '{}' is not assignable to Type P Error

While following along with the Learning React with TypeScript book, I encountered a problem where I hit a wall while coding. Despite surrendering and copying code directly from the book, the compiler is still unhappy As a temporary solution, I have resort ...

Troubles arise when compiling TypeScript to JavaScript

I have been experimenting with TypeScript, specifically for working with classes. However, I am facing an issue after compiling my TS file into JS. Below is the TypeScript code for my class (PartenaireTSModel.ts): export namespace Partenaires { export ...

Using TypeScript, take advantage of optional chaining in conjunction with object destructuring

After updating typescript to version 3.7.4, I find myself trying to modify my code. My code is straightforward: interface Test event: { queryStringParameters: { [name: string]: string } | null; } } const test:Test = (event) => { // const { n ...

Issue with Angular 8: click event is not triggering when using ngFor directive to iterate through arrays of objects

Update: The original post has been modified to omit implementation details and complexity. I am facing an issue with an ngFor loop that invokes a method on a service. The method returns an array which is then iterated over by the for loop. The click even ...

"What is the best way to apply multiple filters to an array in a React

Is there a way to incorporate dropdown menus along with search text input for filtering an array in React? I would like to give users the option to select a location and then search for specific results within that location. Any suggestions on how to ach ...