Typescript how to declare a method that returns the object it's called for (this)

How to write a declaration for on function. The on function has to return a dynamic type based on the object it's called for.

function on(type, handler) {
    this.addEventListener(arguments);

    return this;
}

var element = document.getElementById('elm');
var request = new XMLHttpRequest();

element.on = on; // => element.on(t,cb) must return element 
request.on = on; // => request.on(t,cb) must return request 

Here is my d.ts (this is just example, this is incorrect at ThisType)

declare function on<K extends keyof WindowEventMap>(type: string, handler: 
(this: ThisType, ev: WindowEventMap[K]) => any): ThisType;

I don't know if Typescript has another keywords to solve this problem. (ThisCaller)

Upvotes: 0

Views: 428

Answers (1)

artem
artem

Reputation: 51589

TypeScript has a feature called polymorphic this types which can be used here.

However, this assignment

element.on = on;

will be marked as error because in TypeScript, you can't add arbitrary property to an object just by assigning to that property.

You can do that in a type-safe way by adding a function that will add on property to an object, and return that same object but declared with appropriate type, telling the compiler that it now has that on property:

interface EventListeners {
    addEventListener(type: string, handler: () => void): void;
};

function on<T extends EventListeners>(this: T, type: string, handler: () => void): T {
    this.addEventListener(type, handler);

    return this;
}


var element = document.getElementById('elm');
var request = new XMLHttpRequest();

// had to declare this interface in order to use this as return type
interface EventListenersExt extends EventListeners {
    on(type: string, handler: () => void): this;
}
function addOn<T extends EventListeners>(t: T): T & EventListenersExt {
    return Object.assign(t, { on });
}

const elementExt = addOn(element); 
const requestExt = addOn(request);

const e = elementExt.on('click', () => { }).on('keyup', () => { }); // const e: HTMLElement & EventListenersExt
const r = requestExt.on('load', () => { }); // const r: XMLHttpRequest & EventListenersExt  

Upvotes: 1

Related Questions