The parameters of a generic class in Typescript are customizable and

Currently working on programming an internal abstract class for a project, and I need it to be generic in order to make it extendable.

The goal is to have my class named as if it were extending the T template, like Sample extends T, so that all parameters of T are included. For example, if T is Vue, then all Vue parameters such as $el or $options should be accessible without needing to re-declare or include Vue again.

Here's what I currently have:

export namespace SDK {

  export abstract class Sample<T> {

    private static methods: any = {
      hello: Sample.prototype.hello
    }

    abstract callMe(): void;

    x: number = 0;
    y: number = 0;
    width: number = 1;
    height: number = 1;

    hello(): void {
      console.log('Sample hello world');
      this.callMe();
    }
  }
}

However, I'm unsure how to go about including the properties of T into Sample.

I would like it to look something like this:

export namespace SDK {

  export abstract class Sample<T> {

    private static methods: any = {
      hello: Sample.prototype.hello
    }

    abstract callMe() : void;

    x: number = 0;
    y: number = 0;
    width: number = 1;
    height: number = 1;

    // T properties (example with Vue)
    $el: HTMLElement
    ...

    hello () : void {
      console.log('Sample hello world')
      this.callMe()
    }
  }
}

The desired way to use my class would be like this:

export default class MyComponent extends SDK.Sample<Vue> {
  name: string = 'my-component';

  callMe (): void {
    console.log('called')
  }

  mounted (): void {
    this.hello()
  }
}

I haven't been able to find any information on how to properly extend from a templated class with parameters included in it.

Answer №1

Utilizing mixins, @TitianCernicovaDragomir appears to have the right approach. I have my own code that I will share for thoroughness, as our methods differ slightly with their own set of advantages and disadvantages.

The code below enforces implementation of abstract methods and grants access to static members. However, this comes at the cost of using private unexported names, which restricts the ability to use this as a library for others. While there may be workarounds, delving too deep into them might not be ideal.

Here is the Sample code snippet:

export namespace SDK {

  export type Constructor<T> = {
    new(...args: any[]): T;
    readonly prototype: T;
  }

  export function Sample<C extends Constructor<{}>>(ctor: C) {
    abstract class Sample extends ctor {
      private static methods: any = {
        hello: Sample.prototype.hello
      }
      abstract callMe(): void;
      x: number = 0
      y: number = 0
      width: number = 1
      height: number = 1

      hello(): void {
        console.log('Sample hello world')
        this.callMe()
      }

    }
    return Sample;
  }

}

Here's how it can be used:

export default class MyComponent extends SDK.Sample(Vue) {
  name: string = 'my-component';

  callMe () : void {
    console.log('called')
  }

  mounted () : void {
    this.hello()
  }
}

Best of luck!

Answer №2

In Typescript (as well as in C# and Java), extending a generic parameter directly is not allowed. However, there is a workaround using the concept of mixins.

Here's a solution based on your requirements. Although I haven't tested it with Vue specifically, the methods and properties should be properly inherited from both classes, making it compatible with Vue (feel free to notify me if you encounter any issues).

Two limitations were identified:

  1. You won't receive compiler warnings for failing to implement abstract methods
  2. You cannot access static methods through the derived class

The following function enables this functionality:

function extendSample<T, R extends { new(): T & Sample }>(componentCtor: new () => T): R {
    // Create a new derived class from the component class
    class DerivedComponent extends (<new () => any>componentCtor) {
        constructor() {
            // Call the component constructor
            super();
            // Call the sample class constructor
            Sample.apply(this)
        }
    }
    // Copy instance methods to DerivedComponent
    Object.getOwnPropertyNames(Sample.prototype).forEach(name => {
        DerivedComponent.prototype[name] = Sample.prototype[name];
    });
    return <R><any>DerivedComponent;
}

Example code snippet:

export abstract class Sample {
    abstract callMe(): void;
    x: number = 0
    y: number = 0
    width: number = 1
    height: number = 1

    hello(): void {
        console.log('Sample says hello')
        this.callMe()
    }   
}

export class LibaryComponentBase {
    constructor() {
        this.$el = "HTML "
    }
    $el: string;
    public libraryMethod() {
        console.log("libraryMethod");
    }
    static Test() {

    }
}

export default class MyComponent extends extendSample(LibaryComponentBase) {
    name: string = 'my-component';
    constructor() {
        super();
        console.log(this.$el);
        this.libraryMethod();
        this.hello();
    }

    callMe(): void {
        console.log('callMe method called')
    }

    mounted(): void {
        this.hello();
    }
}

let my = new MyComponent();
my.callMe();

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

Exploring data retrieval from nested arrays of objects in TypeScript/Angular

I have an API that returns an object array like the example below. How can I access the nested array of objects within the JSON data to find the role with roleid = 2 when EmpId is 102 using TypeScript? 0- { EmpId: 101, EmpName:'abc' Role : ...

Unable to bring in Vue component from NPM module

Hello there, I recently developed my own npm package for a navigation bar and I need to incorporate it into my main codebase. Currently, I am utilizing vue @components but I am struggling with integrating the imported component. If anyone has insight on h ...

What is the best way to create an interface with multiple property types in TypeScript?

Is it possible to create an interface property in TypeScript that can hold two different interfaces? interface IPayload1 { id: string; name: string; } interface IPayload2 { id: string; price: number; } interface Demo { // Can we achieve this? ...

What is the best way to combine a Signal containing an array of Signals in Angular using the merge(/mergeAll) operator?

When working in the world of rxjs, you have the ability to combine multiple Observables using the merge operator. If you have an array of Observables, all you need to do is spread that array into the merge operator like this: merge(...arrayOfObservables). ...

implementing firestore onsnapshotListner feature with pagination

I have a web application that needs real-time updates on a large collection of documents. However, due to the size of the collection, retrieving data without applying a limit is not feasible and inefficient. Therefore, it is crucial to implement a query li ...

Tips and tricks for personalizing an npm package in vue.js

I've been working on customizing a multiselect package to incorporate tab events in vuejs, but so far I haven't seen any changes reflected. I'm looking for guidance on how to modify a library within Vue. My approach was to navigate to the n ...

Using TypeScript generics to add constraints to function parameters within an object

My Goal: Imagine a configuration with types structured like this: type ExmapleConfig = { A: { Component: (props: { type: "a"; a: number; b: number }) => null }; B: { Component: (props: { type: "b"; a: string; c: number }) =& ...

Converting enum flags to strings

I'm encountering an issue with converting enum flags to strings and parsing them. Here is an example of my enum: enum CookieOptions { None = 0, Necessary = 1 << 0, Analytical = 1 << 1, Marketing = 1 << 2, Personalization = ...

Create an interface that inherits from a class

I am currently in the process of converting some code that attempts to create an instance of http.ServerResponse. However, typescript is throwing an error: [ts] Property 'ServerResponse' does not exist on type 'typeof "http"'. I hav ...

Error message: 'React TypeScript: (props: OwnProps) => Element' cannot be assigned to type 'FunctionComponent<{}>'

Everything seems to be working fine as a JSX element... interface OwnProps { children: JSX.Element; location: Location; } export function Layout(props: OwnProps): JSX.Element { However, when I convert it into a functional component, an error occurs ...

Angular - tracking the window scroll event to target a specific scrollbar on a page with multiple scrollbars

I am facing difficulty in accessing a specific scrollbar from a component in my web page. The page contains multiple scrollbars, and I need to target and modify the position of a particular scrollbar (scrollTop). I have tried implementing the following co ...

Utilizing RxJS finalize in Angular to control the frequency of user clicks

Can someone provide guidance on using rxjs finalized in angular to prevent users from clicking the save button multiple times and sending multiple requests? When a button click triggers a call in our form, some users still tend to double-click, leading to ...

Unit test failing due to Vue v-model binding not functioning as expected

I am attempting to monitor the value of the #username element when I change the data in form.username // Regitser.vue ... <div class="form-group"> <label for="username">Username</label> <input type="text ...

Quasar Table fails to update (version 1)

The Quasar Table I am using has its data property connected to a Vuex state, which is an array of objects. However, when the state of an object (a row in the table) changes, this change is not reflected in the table itself. Even though the QTable Data prop ...

Automatic type inference for TypeScript getters

It appears that Typescript infers field types based solely on the private variable of a field, rather than taking into account the getter's return type union (1) or inferring from the getter itself (2): test('field type inference', () =& ...

The function cannot be invoked. The 'Boolean' type does not have any call signatures. An error has occurred in the computed property of Vue3

Currently, I am working on creating a computed property that checks if an item is in the array. The function I have created returns a boolean value and takes one parameter, which is the item to be checked. isSelected: function (item: MediaGalleryItemTypes) ...

Sending parameters to a function within the ngAfterViewInit() lifecycle method in Angular version 6

As a beginner in coding, I have created a canvas in Angular and am attempting to pass data received from my server to a function within ngAfterViewInit(). When I hard code the values I want to pass, everything works perfectly. However, if I try to pass da ...

Developing a Javascript object using Typescript

Trying my hand at crafting a TypeScript object from JavaScript. The specific JavaScript object I'm attempting to construct can be found here: https://cdnjs.cloudflare.com/ajax/libs/chess.js/0.10.2/chess.js In the provided JavaScript example, the obj ...

How can jsPDF be used with Angular2 in Typescript?

Recently, I developed an Angular2 application that is capable of generating JSON data. My main goal was to store this JSON output into a file, specifically a PDF file. This project was built using Typescript. To achieve the functionality of writing JSON d ...

Angular's Spanning Powers

How can I make a button call different methods when "Select" or "Change" is clicked? <button type="button" class="btn btn-default" *ngIf="!edit" class="btn btn-default"> <span *ngIf="isNullOrUndefined(class?.classForeignId)">Select</spa ...