Dynamically add functionality to base instance through derived class during execution

In my current project, I am dealing with a class hierarchy that looks like this:

class Base {
    public BaseMethod() {
        // doesSomeStuff
    }
}

class Derived extends Base {
    constructor() {
        super();
    }
    public DerivedMethod() {
        // do some stuff
        BaseMethod();
    }
}

The issue I'm facing is that during runtime, I encounter an instance of the Base class and I need to somehow extend that instance to be of type Derived. Unfortunately, I don't have control over the construction of Base nor am I the owner of the class itself.

I'm trying to figure out how I can dynamically extend that instance at runtime so that it transforms into an instance of type Derived.

function someFunction(base: Base) {
    let derived: Derived = extend(base);
    derived.BaseMethod();
    derived.DerivedMethod();
}
function extend(base: Base): Derived {
    // What should I implement here?
}

PS: It's important that I target ES5 for this solution!

Answer №1

In ES6, it is now feasible to utilize Object.setPrototypeOf for achieving this functionality. By specifying an asserts return type in the extend function, one can employ control-flow narrowing instead of simply returning the input with a more rigid type:

function extend(base: Base): asserts base is Derived {
    Object.setPrototypeOf(base, Derived.prototype);
}

let b: Base = new Base();

extend(b);

b.DerivedMethod(); // This will be allowed due to control-flow narrowing

Note: Care must be taken as this approach could result in creating objects with invalid states. For instance, if the Derived class introduces its own property like foo, using setPrototypeOf will yield a Derived object lacking that property. It becomes imperative to ensure that extend incorporates any necessary new properties for the Derived object.

Playground Link


If your target includes versions earlier than ES6, there exists a polyfill. However, this polyfill is only functional in Google Chrome and Mozilla Firefox. It operates by manipulating the __proto__ property, which was not standardized prior to ES6 - hence why other browsers do not support it. Unfortunately, expanding the polyfill to cater to ES5 on other browsers seems unattainable given the lack of alternative methods for accessing or setting an object's prototype in those environments.

Another potential workaround involves monkey-patching, yet confirming obj instanceof Derived will evaluate to false, indicating that it may not entirely represent an instance of Derived.

Answer №2

I discovered a solution that met my needs inspired by @kaya3's response. If you're not concerned about supporting older browsers, I recommend checking out their answer!

For those who still need support for legacy IE browsers, there is a workaround available for Object.setPrototypeOf that somewhat functions in browsers like IE9. You can find it in the es6-sham. Just keep in mind that references of the object will need to be reassigned.

This is how I approached the issue:

class Base {
    public BaseMethod() {
        console.log('base called');
    }
}

class Derived extends Base {
    constructor() {
        super();
    }
    public DerivedMethod() {
        this.BaseMethod();
        console.log('derived called');
    }
}

function extend(base: Base): Derived {
    return <Derived>(Object.setPrototypeOf(base, Derived.prototype));
}
function assert(base: Base): asserts base is Derived { }

let b: Base = new Base();

b = extend(b);
assert(b);

b.BaseMethod(); // base called
b.DerivedMethod(); // base called derived called

I'm not entirely pleased with the empty assert function. I attempted to integrate it into the extend function, but struggled to combine the return types asserts base is Derived and Derived.

In case anyone is curious, I made the asserts function generic so that it can be used for other types as well:

function assertExtends<T, U extends T>(t: T): asserts t is U { }
b = assertExtends<Base, Derived>(b);

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

Searching for a string within a JSON object in Angular: step-by-step guide

JSON Data Example { "rootData": { "test1": { "testData0": "Previous information", "testData1": "Earlier Information" }, "test2": { "testData0": ...

Could anyone assist me in defining this particular typescript?

for (let i = 0; i < 10; i++) { setTimeout(function() { console.log(i); }, 100 * i); } Upon execution, it prints the following output sequentially: 0 1 2 3 4 5 6 7 8 9 However, the concept of multiplying i by 100 in the setTimeout function may s ...

Angular automatically interprets strings as dates

Currently, I have numerous entities that have been defined: export class Meeting implements IHasId { id = 0; locationId = 0; meetTime = new Date(); isFinalized = false; imageId: number = null; description = ''; name = ...

Creating a unique user interface for VSCode extension

Recently, I've delved into the world of developing extensions for Visual Studio. Unfortunately, my expertise in TypeScript and Visual Studio Code is quite limited. My goal is to create an extension that mirrors the functionality of activate-power-mod ...

Bringing TypeScript modules from a local module into a React application

As I work on organizing my projects and keeping logic separate in components that will eventually be published, I have a specific structure set up for now: I have a library of Typescript scripts within a project named project-a A separate React app create ...

Using Try...catch compared to .catch

Within my service.ts file, I have various user service functions that handle database operations. export const service = { async getAll(): Promise<User[]> { try { const result = await query return result } catch (e) { rep ...

Angular loop using an HTTP GET request is producing garbled information

Currently, I have a loop that includes an http GET request. This is the sample loop code: for (let index = 0; index < datas.length; index++) { let car = datas[index].smiles; console.log('or--> ' + car); this.subscr = this.CarServ ...

Develop an "Import Interface" using TypeScript

I have a large project with many files and I believe using an import object would be beneficial. For instance, consider having menu.ts at the top level that every program will refer to: import router from "./router/index"; import controllers from ...

Error message: Duplicate identifier found in Typescript

Encountering an error while trying to run my angular-meteor client (ionic serve), specifically: [00:29:20] typescript: node_modules/meteor-typings/1.3/main.d.ts, line: 657 Duplicate identifier 'Status'. L657: type Status ...

What is the best way to duplicate a Typescript class object while making changes to specific properties?

I have a Typescript cat class: class Kitty { constructor( public name: string, public age: number, public color: string ) {} } const mittens = new Kitty('Mittens', 5, 'gray') Now I want to create a clone of the inst ...

Contrasting `Function` with `(...args: any[]) => any`

Can you explain the difference between Function and (...args: any[]) => any? I recently discovered that Function cannot be assigned to (...args: any[]) => any. Why is that so puzzling? declare let foo: Function; declare let bar: (...args: an ...

what is the best way to determine the variable type in typescript and angular?

How can I determine the type of a variable in TypeScript with Angular? import { Component } from '@angular/core'; interface Abc { name : string } @Component({ selector: 'my-app', templateUrl: './app.component.html', ...

"HandsOnTable, showcasing its capability to effortlessly load data containing formulas

I am working with a HandsonTable that receives data in the form of an array of arrays. After applying some formulas and saving the data, I am now facing an issue while trying to load the data directly into the HandsonTable component in ReactJs. The problem ...

Troubleshooting a NextJS Middleware Issue: Retrieving a Blank Screen After Token Verification

I'm currently developing a Spotify Clone using its API and utilizing Next JS Middleware to enforce redirect conditions. The purpose of these conditions is to verify if the user has a token; if not, they should be redirected to the login page. For som ...

Create dynamic modals in ReactJS that appear when a row is clicked

Engaged in a project for an undisclosed entity where patient data is retrieved from their API and displayed as modal on the page. Clicking on a modal reveals more threat information in another modal. The objective is for these modals to render based on a c ...

Exploring the Implementation of Routing in AngularJS 2

I recently started working on my first AngularJS 2 application and I am currently in the process of setting up routing. I followed an example from the official AngularJS 2 documentation website to create a simple Product CRUD application, but I'm enco ...

Errors related to Typescript are causing issues with the stock installation of Next.js

Whenever I use typescript with create-next-app, my tsx files are filled with numerous "Problems" messages. These errors only appear in Visual Studio Code and do not affect the build process. I have tried uninstalling vscode, removing all extensions, and ...

Issue: TypeScript is throwing an error of type TypeError because it is unable to read the property "push" of an undefined

I am encountering a common issue while working with TypeScript. The error message I am receiving is: ERROR TypeError: Cannot read property 'push' of undefined In my code, I have defined a model called EmailModel: export class EmailModel { ...

Modifying app aesthetics on-the-fly in Angular

I am currently working on implementing various color schemes to customize our app, and I want Angular to dynamically apply one based on user preferences. In our scenario, the UI will be accessed by multiple clients, each with their own preferred color sch ...

Strategies for splitting a component's general properties and accurately typing the outcomes

I am attempting to break down a custom type into its individual components: type CustomType<T extends React.ElementType> = React.ComponentPropsWithoutRef<T> & { aBunchOfProps: string; } The code appears as follows: const partitionProps = ...