Discovering the interface type of class properties in order to implement a factory method

I am struggling with implementing a factory method in my code. I want to be able to pass not only a Class type to instantiate but also a set of default values for the properties within the class. My goal is to have the compiler notify me if I try to pass in properties that do not exist in the class type being instantiated. However, I can't seem to figure out how to achieve this.

Here is a basic example illustrating what I am trying to accomplish:

When attempting this, I encounter a compiler error stating: "An interface may only extend a class or another interface." for the interface ClassProps since it cannot extend from T.

class Base {
   myId: number;
}

class Klass1 extends Base {
   myString: string;
   myNumber: number;
}

interface IBaseCtr<T> {
   new (): T;
}

// Obtain an interface representing the properties of the passed class
interface ClassProps<T extends Base> extends T {}

function factory<T extends Base>(ctrFunc: IBaseCtr<T>, initialData?: ClassProps<T>): T {
   let new_obj = new ctrFunc();
   Object.assign(new_obj, initialData);
   return new_obj;
}

let v1 = factory(Klass1, {myId: 10, myString: 'foo'});
let v2 = factory(Klass1, {badVar: 10});

Do you have any suggestions on how to type initialData so that it will highlight badVar as not permissible in the second call?

Answer №1

This question is truly delightful.

My approach here is to eliminate that interface completely.

function createInstance<T extends V, V extends Base>(ctrFunc: IBaseCtr<T>,
                                 initialData?: V): T
{
   let new_obj = new ctrFunc();
   Object.assign(new_obj, initialData);
   return new_obj;
}

In this setup, Base represents a subset of properties in V, which in turn is a subset of properties in T (the final type). The assignment of v1 works smoothly. However, the statement assigning v2 triggers a compile-time error.

If you wish, you may remove the extends Base part to broaden the applicability of this function. It will still undergo the same type checking process.

Just for fun, here's an entirely separate factory function signature that passes the type checking:

function createInstance<T extends V, V>(ctrFunc: new () => T,
                                 initialData?: V): T

Answer №2

My approach differs slightly from yours, but here is the code:

interface BasicProperties {
    id: number;
}

class BaseClass<T extends BasicProperties> {
    protected id: number;

    initialize(properties: T): void {
        this.id = properties.id;
    }
}

interface MyClassProperties extends BasicProperties {
    valueString: string;
    valueNumber: number
}

class MyClass extends BaseClass<MyClassProperties> {
    private valueString: string;
    private valueNumber: number;

    initialize(properties: MyClassProperties): void {
        super.initialize(properties);

        this.valueString = properties.valueString;
        this.valueNumber = properties.valueNumber;
    }
}

interface IBaseConstructor<T> {
    new (): T;
}

function createInstance<P extends BasicProperties, T extends BaseClass<P>>(constructorFunc: IBaseConstructor<T>, initialData?: P): T {
    let newObj = new constructorFun();
    newObj.initialize(initialData);
    return newObj;
}


let instance1 = createInstance(MyClass, {id: 10, valueString: 'bar'});

let instance2 = createInstance(MyClass, {badVar: 20});

This fulfills your requirements, where the second call to createInstance results in a compilation error.

I set the class members as private/protected and opted for an initialize method instead of using Object.assign for assigning values.
Why? because it aligns better with object-oriented principles. This way, you can leverage the class hierarchy when setting values by using super.initialize (or choosing not to call it in certain scenarios).
In essence, this provides more flexibility and control.

Check out the code on TypeScript Playground

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

Apply criteria to an array based on multiple attribute conditions

Given an array containing parent-child relationships and their corresponding expenses, the task is to filter the list based on parents that have a mix of positive and negative expenses across their children. Parents with only positive or negative child exp ...

Tips for effectively transmitting data while utilizing a declarative/reactive data access method with RxJS in Angular?

Angular typically follows a classic pattern for data access that looks like this: Traditional Data Access Pattern getProducts(): Observable<Product[]> { return this.http.get<Product[]>(this.productsUrl) .pipe( tap(data => consol ...

Using NextJS's API routes to implement Spotify's authorization flow results in a CORS error

I am currently in the process of setting up the login flow within NextJS by referring to the guidelines provided in the Spotify SDK API Tutorial. This involves utilizing NextJS's api routes. To handle this, I've created two handlers: api/login.t ...

Execute supplementary build scripts during the angular build process

I've developed an Angular application that loads an iframe containing a basic html page (iframe.html) and a Vanilla JavaScript file (iframe.js). To facilitate this, I've placed these 2 files in the assets folder so that they are automatically cop ...

When incorporating an array as a type in Typescript, leverage the keyof keyword for improved

I am facing a situation where I have multiple interfaces. These are: interface ColDef<Entity, Field extends keyof Entity> { field: Field; valueGetter(value: Entity[Field], entity: Entity): any } interface Options<Entity> { colDefs ...

Using TypeScript with Next.js getStaticProps causes errors

Currently, I am grappling with utilizing getStaticProps along with TypeScript. Initially, I attempted to achieve this using a function declaration: import { Movie } from './movies/movie' import { GetStaticProps } from 'next' export asy ...

What is the reason behind installing both Typescript and Javascript in Next.js?

After executing the command npx create-next-app --typescript --example with-tailwindcss my_project, my project ends up having this appearance: https://i.stack.imgur.com/yXEFK.png Is there a way to set up Next.js with Typescript and Tailwind CSS without i ...

Angular findIndex troubleshooting: solutions and tips

INFORMATION = { code: 'no1', name: 'Room 1', room: { id: 'num1', class: 'school 1' } }; DATABASE = [{ code: 'no1', name: 'Room 1', room: { id: 'num1', ...

Issue with Material UI DateTimePicker not submitting default form value

Currently, I am utilizing React for my frontend and Ruby on Rails for my backend. My issue lies in submitting the value from my materialUI DateTimePicker via a form. The problem arises when I attempt to submit the form with the default DateTime value (whic ...

Tips for sending a timestamp as input to a stored procedure in TypeScript with the mssql module

Currently, I am utilizing the mssql npm package to communicate with SQL server. I have encountered a dilemma where the variable (which is of type TIMESTAMP in sql server) fetched from a stored procedure is returned as a byte array. Now, I need to pass this ...

What is the correct way to import scss files in a Next.js project?

Currently, I am working on a NextJS project that uses Sass with TypeScript. Everything is running smoothly in the development environment, but as soon as I attempt to create a build version of the project, I encounter this error. https://i.stack.imgur.com ...

Generate a fresh array by filtering objects based on their unique IDs using Angular/Typescript

Hey there, I am receiving responses from 2 different API calls. Initially, I make a call to the first API and get the following response: The first response retrieved from the initial API call is as follows: dataName = [ { "id": "1", ...

Simulating TypeDI service behavior in Jest

My current setup includes Node with TypeScript, TypeDI and Jest. I've been working on creating services that have dependencies on each other. For example: @Service() export class MainService{ constructor(private secondService: SecondService){} public ...

Retrieve the final variable in an Observable sequence

In my code, I have a variable called 'messages' which stores messages from a conversation: messages: Observable<Message[]>; To populate the 'messages' variable, I do the following: const newMessage = new Message(objMessage); ne ...

Discovering the import path of Node modules in ReactAlgorithm for determining the import path of

Software Development In my current project, I am utilizing Typescript along with React. To enhance the application, I integrated react-bootstrap-date-picker by executing yarn install react-bootstrap-date-picker. Unfortunately, there is no clear instruct ...

Utilizing Typescript's type inference within a universal "promisify" function

Unique Context Not long ago, I found myself delving into the world of "promisification" while working on a third-party library. This library was packed with NodeJS async functions that followed the callback pattern. These functions had signatures similar ...

Angular 5 - Jasmine Tests explained: Encounter with the puzzling error message: "Error: Provider for the NgModule 'DynamicTestModule' is invalid, as only instances of Provider and Type are permitted"

I'm having trouble running tests on a component class. Here's the error message from the stack: Error: Invalid provider for the NgModule 'DynamicTestModule' - only instances of Provider and Type are allowed, got: [AlertModaldataCompon ...

Substitute all attributes of objects with a different designation

I need to update all object properties from label to text. Given: [ { "value": "45a8", "label": "45A8", "children": [ { "value": "45a8.ba08", "label": "BA08", &q ...

Error in Angular 2 after transition to @types: encountering "require" name not found issue

After transitioning my project from old typings to types-publisher, I have successfully resolved most of my dependencies. However, I am consistently encountering an error that reads Cannot find name 'require'. Below is a snippet from my tsconfig. ...

Passing a custom data type from a parent component to a child component in React

I'm currently working on developing a unique abstract table component that utilizes the MatTable component. This abstract table will serve as a child element, and my goal is to pass a custom interface (which functions like a type) from the parent to t ...