Extract data from a superclass to a subclass

Consider the following scenario where a base class is defined:

class BaseComponent<T> {
    constructor(protected selectors: T) {}
}

LoginComponent.ts

const ADMIN_SELECTORS = {
    inputUserName: "foo",
    buttonLogin: "bar",
    textError: "baz",
};


class LoginComponent<T> extends BaseComponent<typeof ADMIN_SELECTORS> {
    constructor(protected selectors: typeof ADMIN_SELECTORS) {
        super(selectors);
    }

    get loginButton() {
        return this.selectors.buttonLogin;
    }
}


// EXAMPLE CALL
const login = new LoginComponent(ADMIN_SELECTORS);

The use of typeof ADMIN_SELECTORS in the code above serves the purpose of informing TypeScript about the structure of this.selectors. This enables autocomplete and type checking for this.selectors.

get userInput() {
        return this.selectors.inputUser; // Property 'inputUser' does not exist on type '{ inputUserName: string; buttonLogin: string; textError: string; }'
    }

Excellent!

Now, the task at hand is to create a subclass called CustomerLoginComponent. This subclass should have a different value for inputUserName and introduce a new unique property called textCustomerName in its selectors object.

CustomerLoginComponent.ts

const CUSTOMER_SELECTORS = {
    inputUserName: "abc",
    textCustomerName: "xyz",
};

class CustomerLoginComponent extends LoginComponent<typeof CUSTOMER_SELECTORS> {
    constructor(selectors) {
        super(selectors);
    }

    get customerName() {
        return this.selectors.textCustomerName;
    }
}

// EXAMPLE CALL
const customerLogin = new CustomerLoginComponent(CUSTOMER_SELECTORS);

An error arises with the message

Property 'textCustomerName' does not exist...
in the code segment below:

 get customerName() {
        return this.selectors.textCustomerName;
    }

To resolve this, a modification is made within LoginComponent:

class LoginComponent<T> extends BaseComponent<typeof ADMIN_SELECTORS> {
    constructor(protected selectors: typeof ADMIN_SELECTORS & T) { // Note ' & T'!
        super(selectors);
    }

    get loginButton() {
        return this.selectors.buttonLogin;
    }
}

Subsequently, no errors are encountered when working with CustomerLoginComponent.

However, defining the annotation for selectors in CustomerLoginComponent poses a challenge. The suggested approach by TypeScript appears as follows:

class CustomerLoginComponent extends LoginComponent<typeof CUSTOMER_SELECTORS> {
    constructor(selectors: { inputUserName: string; buttonLogin: string; textError: string; } & { inputUserName: string; textCustomerName: string; }) {
        super(selectors);
    }
}

It's impractical to hardcode the object structure due to its flexible nature (hence the use of typeof). Moreover, the object resides in a separate file.

The main question remains: can CustomerLoginComponent be aware that the type of selectors comprises two merged objects - one from the parent class (the type of which should be somehow passed) and typeof CUSTOMER_SELECTORS?

Exploring the infer keyword has been attempted, but it may not serve the intended purpose... Furthermore, uncertainties arise in regards to:

class LoginComponent<T> extends BaseComponent<typeof ADMIN_SELECTORS> {
    constructor(protected selectors: typeof ADMIN_SELECTORS & T) {
    }
}

There seems to be an issue in my base class definition:

class BaseComponent<T> {
    constructor(protected selectors: T) {}
}

Note that an explicit annotation of the selectors' type as : typeof ADMIN_SELECTORS in LoginComponent is necessary for proper functioning.

In summary, the example highlighted reveals certain design flaws, thus any assistance in rectifying them would be highly appreciated.

Alternatively, guidance on annotating the type of selectors argument in CustomerLoginComponent is sought.

Answer №1

Generic Concepts

Approaching the concept of generics, it's important to understand the difference between typeof ADMIN_SELECTORS & T. The use of T in both BaseComponent and LoginComponent can lead to confusion as they represent different entities. In BaseComponent, T encompasses all selectors whereas in LoginComponent, the selectors are specifically of type typeof ADMIN_SELECTORS. The generic nature of T in LoginComponent is primarily to accommodate additional properties introduced by subclasses.

T can be utilized to indicate only extra selectors:

class LoginComponent<T = {}> extends BaseComponent<typeof ADMIN_SELECTORS & T> {

Alternatively, T can encompass all selectors:

class LoginComponent<T extends typeof ADMIN_SELECTORS = typeof ADMIN_SELECTORS> extends BaseComponent<T> {

In either case, a default value for T ensures it never becomes unknown.

Constructor Logic

The discussion now shifts to passing arguments to constructors and defining their types. Across all three classes, the constructor accepts the entire selectors object.

This implies that constructing

new CustomerLoginComponent(CUSTOMER_SELECTORS)
isn't feasible as it lacks the admin selectors expected by LoginComponent. Instead, calling
new CustomerLoginComponent({...ADMIN_SELECTORS, ...CUSTOMER_SELECTORS})
would address this issue.

If avoiding such complexity is preferred, a solution involves having classes reference constants for their selectors rather than relying on constructor inputs. While tight coupling between a class and its data isn't ideal, the typing dependency on constant typeof already establishes strong ties.

class BaseComponent<T> {
    constructor(protected selectors: T) {}
}

class LoginComponent<Extra = {}> extends BaseComponent<typeof ADMIN_SELECTORS & Extra> {
    constructor(extraSelectors: Extra = {} as any) { 
        super({...extraSelectors, ...ADMIN_SELECTORS});
    }
}


class CustomerLoginComponent extends LoginComponent<typeof CUSTOMER_SELECTORS> {
    constructor() {
        super(CUSTOMER_SELECTORS);
    }
}

// EXAMPLE CALL
const login = new LoginComponent();
const customerLogin = new CustomerLoginComponent();

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

Are TypeScript Date Formatting options comparable to those found in .NET?

When working with C# .NET, adjusting the Format of a date is simple: DateTime date = new DateTime(); string date1 = date.ToString("dd MMM yy"); string date2 = date.ToString("yyyy/MM"); string date3 = date.ToString("MMMM d, yyyy"); However, I'm curio ...

Steer clear of receiving null values from asynchronous requests running in the background

When a user logs in, I have a request that retrieves a large dataset which takes around 15 seconds to return. My goal is to make this request upon login so that when the user navigates to the page where this data is loaded, they can either see it instantly ...

I encountered an issue when trying to import a React.js component in TypeScript. The .d.ts file was created by myself, but I received the error message "Module not found: Can't resolve 'xxx' in 'xxx'"

I included the TypeScript definition file for a component in my project: /// <reference types="react" /> declare namespace __ReactAvatarEditor { interface croppingRect { x: number, y: number, width: number, height: number } interf ...

Encountering errors with abstract keyword in TypeORM while implementing concrete table inheritance is a common issue

Looking for some guidance on class inheritance in TypeORM. Currently, I am trying to implement concrete table inheritance as outlined here: https://github.com/typeorm/typeorm/blob/master/docs/entity-inheritance.md#concrete-table-inheritance. However, I am ...

Avoid stopping Bootstrap Vue's events

Need help with a b-form-select control in Bootstrap Vue. Trying to execute a function when the value changes, but want the option to cancel the event and keep the original value. Complicating matters, the select is in a child component while the function ...

Can you explain the purpose of the 'as' keyword in Angular?

The use of the 'as' keyword in the code snippet above has caused some confusion for me. I am wondering why, if this.form.get('topics') is already a formArray object, it needs to be returned as a FormArray again. This seems redundant to ...

In an array where the first 3 images have been filtered using an if statement, how can I show the image at the 3rd index (4th image) starting from the beginning?

In my personal web development project, I'm using AngularJS for the front-end and .NET Core for the back-end to create a website for a musical instrument retailer. The model I'm working with is called "Guitar" and all the guitar data is stored in ...

Using the esnext compiler option in Typescript causes issues with importing ES6 modules from external libraries

When I specify either the module or target property in the compiler options to esnext (so I can use import("example") statements), my es6 import statements for npm installed libraries stop working (local modules like "./test.ts" still work). For example, ...

Having trouble uploading an image using Angular, encountering an error in the process

Whenever I try to upload an image, the server keeps throwing an error saying Cannot read property 'buffer' of undefined. I am using Node.js as a backend server and interestingly, when I send the image through Postman, it gets stored in MongoDB wi ...

Custom Angular component experiencing issues with displaying pre-selected options

I'm currently working on a component with a multi-select dropdown feature. The goal is to be able to provide a list of objects (specifically ICatagory in my case) for the options when creating an instance of this component. Additionally, I want to pas ...

Unusual function call patterns observed in generic classes

After compiling the code below, it runs without any issues interface A { x: string; } interface B { x: string; } type C<D extends A> = D | B; declare function funcA<D extends A, E extends C<D> = C<D>>(): E | E[] | undefined; d ...

Error in React TypeScript: The onCreate function is causing an overload error. Specifically, overload 1 of 2, with 'props: Readonly<{}>', is creating issues

Why does it work in Javascript but show an error in TypeScript? How can I fix this? App.tsx class App extends React.Component<{}, null> { handleCreate = (data) => { console.log(data); } render() { return ( ...

The functionality of GetStaticProps with Typescript is only operational when defined as an arrow function, rather than a function

The documentation for GetStaticProps in NextJs explains it as a function declaration. When trying to add types to it, the following code snippet results: export async function getStaticProps(): GetStaticProps { const db = await openDB(); const fa ...

What should the return type be for a Typescript node express controller?

What type of return function should I use in Node Express? I have come across information suggesting that I can use either any or void. However, using void is not recommended in this case. Can anyone provide some suggestions on the best approach? Current ...

Iterate through a type object to identify any undefined properties and then transfer them to another class object with matching fields

In my application, I have a class used for the database and a type received from the frontend. The database class structure is as follows: @ObjectType() @Entity() export class Task { @Field(() => Int) @PrimaryKey() id!: number; @Field(() => ...

Converting literal types within simulated JSON data

I'm currently working with a JSON object that looks like this: { "elements": [ { "type": "abstract" }, { "type": "machine" }, { "type": "user" ...

Verify registration by sending an email to an alternate email address through Angular Firebase

I have implemented email verification for users before registration. However, I would like to receive a verification email to my own email address in order to finalize the registration process. I want to be notified via email and only after my approval sho ...

What is the best way to confirm that a certain element is not present on the page using playwright?

My current challenge involves testing a website that features a logo, and I need to ensure that the logo does not display on specific pages. I have been exploring the Playwright assertions documentation for guidance on how to assert that an element does N ...

Encountered an issue trying to access undefined properties while reading 'PP'

I am trying to showcase the data retrieved from my JSON file. Here is a glimpse of the information stored in the JSON => Within DTA > PP , I am specifically interested in displaying the variable NOMFAMILLE. An error message has caught my attentio ...

Error message stating 'is not recognized' caused by Angular SharedModule

I have a navbar component that I've organized into a module called 'NavbarModule' so that it can be easily shared. My goal is to use this component in the 'ProductsListComponent'. However, even after properly importing and exportin ...