Ways to set all class properties as optional in Typescript

Is there a specific way to structure this data?

interface ProductDTO {
    id: string;
    name: string;
    price: number;
}
  
interface UpdatedProductDTO extends Partial<ProductDTO> {}

Alternatively, could it be done using classes like so:

class ProductDTO {
    id: string;
    name: string;
    price: number;
}
  
class UpdatedProductDTO extends Partial<ProductDTO> {}

The challenge arises from needing to utilize classes instead of interfaces in order to incorporate decorators for validation purposes.

Answer №1

Considering the process of converting one class definition (CreateDTO) into another class definition (UpdateDTO) can be problematic. The initial class definition provided as an example, CreateDTO, fails to properly initialize its required properties. Enabling the --strictPropertyInitialization compiler option, which is part of the --strict features suite, leads to identifying and fixing these initialization errors. This necessitates considering how instances of the class should be constructed by passing constructor arguments for the properties:

class CreateDTO {
    id: string;
    name: string;
    price: number;
    constructor(id: string, name: string, price: number) {
        this.id = id;
        this.name = name;
        this.price = price;
    }
}

The approach of extending Partial<CreateDTO> with class UpdateDTO is not straightforward due to limitations of type extension in TypeScript. Instead, a Partial function can be created to extend the class constructors of a generic type T to that of Partial<T>:

function Partial<A extends any[], T extends object>(ctor: new (...args: A) => T) {        
    const ret: new (...args: A) => Partial<T> = ctor;
    return ret;
}

Once the Partial function is applied, it allows creating

class UpdateDTO extends Partial(CreateDTO)
. However, this doesn't completely resolve the issue as the new class still requires constructor arguments inherited from CreateDTO.

Rather than viewing UpdateDTO as directly derived from CreateDTO, a more effective perspective is transforming interface definitions into class definitions. Creating a factory function that takes an object type definition and generates a class constructor provides better flexibility:

function DTOClass<T extends object>() {
    return class {
        constructor(arg: any) {
            Object.assign(this, arg)
        }
    } as (new (arg: T) => T);
}

The use of Object.assign() in the implementation spreads input arguments into the instance being constructed. This approach results in classes like CreateDTO and UpdateDTO produced using the factory function:

interface CreateDTO {
    id: string;
    name: string;
    price: number;
}
const CreateDTO = DTOClass<CreateDTO>();

const c = new CreateDTO({ id: "abc", name: "def", price: 123 });
c.price = 456;
console.log(c); // { "id": "abc", "name": "def", "price": 456 }

}

interface UpdateDTO extends Partial<CreateDTO> { }
const UpdateDTO = DTOClass<UpdateDTO>();
const u = new UpdateDTO({});
u.name = "ghi";
console.log(u); // { "name": "ghi" }

This method ensures compatibility between class constructors and object types both during compilation and at runtime.


An improvement could be made to allow class constructors that accept optional arguments when the object type has no mandatory properties. By introducing a conditional type in DTOClass, the argument requirement becomes optional if an empty object would suffice as input:

function DTOClass<T extends object>() {
    return class {
        constructor(arg: any) {
            Object.assign(this, arg)
        }
    } as {} extends T ? (new (arg?: T) => T) : (new (arg: T) => T);
}

With this modification, classes like UpdateDTO can now be instantiated without requiring any arguments.

Playground link here

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

What is the reason for the index type being defined twice?

Here is an example from the official TypeScript documentation: class Animal { name: string; } class Dog extends Animal { breed: string; } // Error: indexing with a 'string' will sometimes get you a Dog! interface NotOkay { [x: numbe ...

When using Next.js or Express, a TypeScript project will seamlessly integrate as a local dependency during runtime or when building

I am currently developing a project in TypeScript using Next.js, and I've come across a peculiar issue where the project is automatically including itself as a local dependency in the package.json file. Here is an example of what my package.json file ...

Navigating through the key type within a mapped structure

I am working with a mapped type in the following structure: type Mapped = { [Key in string]: Key }; My understanding is that this setup should only allow types where the key matches the value. However, I have found that both of the cases below are permitt ...

When working in React, I often encounter the frustrating TypeError: Cannot read property 'content' of undefined error

Trying to customize a React project, I attempted to add a class to a div using the following code: <div className={styles.content}> In the case of deleting the Data Source, you will lose all configuration sett ...

Webpack 4.1.1 -> The configuration.module contains a property 'loaders' that is unrecognized

After updating my webpack to version 4.1.1, I encountered an error when trying to run it: The configuration object is invalid. Webpack has been initialized with a configuration that does not match the API schema. - The 'loaders' property in ...

Transferring information between databases using node-sqlite - customizing the 'insert' query syntax

I'm currently developing a small tool to transfer data from one sqlite database file to another. Both databases have identical table structures, so the focus is solely on moving rows from one database to another. Here's my current code: let tab ...

Unable to conduct end-to-end testing as Nestjs fails to resolve dependencies

I encountered the following error : Nest is unable to resolve dependencies of the ParametrageRepository (?). Please ensure that the argument DataSource at index [0] is available in the TypeOrmModule context. This is my test code : describe("ParametrageC ...

How to bring in classes and external libraries in Angular 2?

When importing an external class in TypeScript from another class, do I need to create a new instance of the variable before using it inside another class? For example, should I do let cli = new client()? Some tutorials barely mention this step and simply ...

What is the best way to exhibit information from a get request within an option tag?

My GET request is fetching data from a REST API, and this is the response I receive: { "listCustomFields": [ { "configurationType": null, "errorDetails": null, "fieldId" ...

Embedded template does not utilize property binding ngif with any directive

I am currently working on an Angular (Angular2 RC4) application and I'm facing some challenges running it with the live server in nodejs. Any suggestions on how to troubleshoot the error showing up in the Chrome console would be greatly appreciated. ...

Ways to boost the smoothlife performance and framerate in p5js

I have a NextJS project using p5js deployed on . This project is an implementation of , which involves a cellular automata generalized on a continuous domain. Currently, it runs at around 10 to 14 frames per second and I aim to increase this. You can fin ...

Postman won't stop bombarding my Node-express-Typescript application with post requests

While working on my app with node-typescript, I encountered an issue with my post request using Postman. Even though I was able to console log the request body and receive data, Postman kept showing "sending request" along with an image. Despite not findin ...

Exploring FormData Fields in Remix React

Is there a way to retrieve the fields without having to do it individually? const name = (formData.get("name") ?? "") as string; Can we use mapping or iteration instead? CODE export const action: ActionFunction = async ({ request ...

Having trouble utilizing a function with an async onload method within a service in Angular - why does the same function work flawlessly in a component?

I successfully created a component in Angular that can import an Excel file, convert it into an array, and display its content as a table on the page. The current implementation within the component looks like this: data-import.compoent.ts import { Compo ...

Exploring the utilization of an interface or class in Typescript

Imagine a common situation where users need to provide an email and password for logging in using Typescript. To make this process more organized, I want to define a strong type that represents the user's login information and send it securely to the ...

After the curly brace in a type definition, what is the significance of [keyof T]?

After reviewing the Typescript documentation found at this specific URL https://www.typescriptlang.org/docs/handbook/advanced-types.html type FunctionPropertyNames<T> = { [K in keyof T]: T[K] extends Function ? K : never; }[keyof T]; I'm curi ...

Preventing parent requests from being triggered when a child element is clicked in Angular 2

I have a similar code structure as shown below and I am trying to achieve the behavior where clicking on the Child Div does not trigger the Parent Div click event. <div class="parentDiv" (click)="parentDiv()"> <div class="childDiv" (click)="ch ...

"Enhancing User Interfaces with Reactjs and Typescript: Exploring the Border

I am working on a component that takes borderStyle as a prop and then passes it down to a child div. I am trying to specify a type for this prop but I'm struggling to find the right one. Below is a snippet of my code that is relevant to this: inter ...

What is the best way to perform unit testing on a function component that includes React.useState() using jest and enzyme?

I'm working on a function component that utilizes React.useState() to handle the state of a drawer modal. My challenge lies in testing this function and its ability to modify state using jest enzyme, as I cannot access its state function due to it not ...

Is foreach not iterating through the elements properly?

In my code, I have a loop on rxDetails that is supposed to add a new field payAmount if any rxNumber matches with the data. However, when I run the forEach loop as shown below, it always misses the rxNumber 15131503 in the return. I'm not sure what I ...