Creating a TypeScript class constructor that uses an object to define class properties

Within TypeScript, it's feasible to construct a class that includes a constructor accepting parameters with access modifiers. It then automatically converts these parameters into class fields.

class Item {
  constructor(
    public id: number,
    public updatedAt: number,
    public createdAt: number,
  ) {}
}

const item = new Item(1, 1, 1);
item.id // 1

I'm pondering whether there is a method to pass all those parameters within an object instead.

class Item {
  constructor({
    public id: number,
    public updatedAt: number,
    public createdAt: number,
  }) {}
}

const item = new Item({ id: 1, updatedAt: 1, createdAt: 1 });
item.id // 1

Is this achievable? Could it potentially be in the future?

Are there any alternative solutions to achieve something similar?

Answer №1

To simplify the process, one approach is to define the fields within the class and utilize a mapped type as a parameter. Subsequently, employ Object.assign to assign these fields to this. There are various options available for selecting the appropriate mapped type:

Partial<T>

This type encompasses all members (fields and methods) of the class, with each being optional. The drawback here is that it does not allow for setting certain fields as required, potentially enabling the caller to override a method.

class Item {

    public id: number;
    public updatedAt: number;
    public createdAt: number;
    constructor(data: Partial<Item>) {
        Object.assign(this, data);
    }
    method() {}
}

//Works 
const item = new Item({ id: 1, updatedAt: 1, createdAt: 1 });
//This also works unfortunately 
const item2 = new Item({ id: 1, method() { console.log('overriden from param !')} });

Pick<T, K>

With this mapped type, specific properties can be selected from T by specifying a union of string literal types that serve as keys in T. The advantage lies in inheriting whether a field is required or optional from the original declaration in the class via Pick, allowing for some fields to be mandatory while others remain optional. However, duplication of property names is required (once in the class and once in the Pick):

class Item {
    public id: number;
    public updatedAt?: number;
    public createdAt?: number;
    constructor(data: Pick<Item, "id" | "updatedAt" | "createdAt">) {
        Object.assign(this, data);
    }
    method() {}
}
const item = new Item({ id: 1  }); //id is required others fields are not
const item2 = new Item({ id: 1, method() {}  }); // error method is not allowed

Custom Mapped Type That Removes Methods

A third option involves creating a type similar to Pick which includes all class fields but excludes methods automatically. This can be achieved using conditional types in Typescript 2.8 (currently unreleased as of writing). This approach offers the benefits of Pick without the need to specify field names again:

type NonMethodKeys<T> = {[P in keyof T]: T[P] extends Function ? never : P }[keyof T];  
type RemoveMethods<T> = Pick<T, NonMethodKeys<T>>; 

class Item {
    public id!: number;
    public updatedAt?: number;
    public createdAt?: number;
    constructor(data: RemoveMethods<Item>) { // No need to specify field names again
        Object.assign(this, data);
    }
    method() {}
}

const item = new Item({ id: 1  });  //id is required others fields are not
const item2 = new Item({ id: 1, method() {}  }); // error method is not allowed 

Playground Link

Answer №2

To replicate this functionality, consider implementing an interface:

interface ItemProperties {
  id: number;
  lastUpdated: number;
  createdOn: number;
}

class ListItem {
  constructor(public properties: ItemProperties) {}
}

const listItem = new ListItem({ id: 1, lastUpdated: 1, createdOn: 1 });
console.log(listItem.properties.id);

Answer №3

Expanding upon Titian's response, if you prefer not to manually input the code for the RemoveMethods type, another option is utilizing the ConditionalExcept from type-fest's conditional-except.d.ts file:

import { ConditionalExcept } from 'type-fest';

class Item {
  mandatory_attribute!: int;
  nonmandatory_attribute?: string;

  constructor(object: ConditionalExcept<Item, Function>) {
    Object.assign(this, object);
  }
  customMethod() {}
}

Answer №4

This code snippet showcases a neat way to handle optional constructor parameters and default values in TypeScript. The use of the ItemParams interface provides structure and clarity to the data being passed to the constructor. It is worth noting that setting generic types is crucial for proper type checking.

interface ItemParams {
  id: number;
  updatedAt?: number;
  createdAt: number;
}

interface Item extends ItemParams { };
class Item {
  constructor(params: ItemParams) {
    // Setting default values using spread syntax
    Object.assign<this, ItemParams>(this, {
      updatedAt: new Date().getTime(),
      ...params
    });
  }
};

export { Item };

Usage

const item = new Item({ id: 1, createdAt: 1 });
console.log(JSON.stringify(item, null, 4));

Output

{
    "updatedAt": 1719091079502,
    "id": 1,
    "createdAt": 1
}

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

Encountering an issue when trying to generate a button in Angular

I am currently using JavaScript to dynamically create a button in Angular. While I have been successful in creating the button, I am encountering an error when attempting to change the classname. The error message I am receiving is: Property 'clas ...

Is there a way for me to use TypeScript to infer the type of the value returned by Map.get()?

type FuncType<O extends Object> = (option: O) => boolean export const funcMap: Map<string, Function> = new Map() const func1: FuncType<Object> = () => true const func2: FuncType<{prop: number}> = ({ prop }) => prop !== 0 ...

Tips for handling trycatch block and generics with axios

Seeking advice on handling Axios and typescript errors. Issue arises with returning errors due to a Type error involving Promise not being of type number | undefined, specifically originating from the catch block. How can I elegantly manage this scenario? ...

Formik Fields with unique key properties

When mapping text fields, I follow this structure: { AddVehicleFields.map(({formikRef, ...input}) => ( <> <TextField key={formikRef} helperText={ getIn(formik.touched, formikRef) ? getIn(formik. ...

Tips for populating all the ionic form fields using speech recognition technology

In the process of developing an Ionic 4 application, I am faced with a challenge where I need to fill in multiple form fields using the Ionic speech-recognition plugin. Currently, I am only able to populate one field at a time. What I am looking for is a w ...

The type 'Observable<HttpEvent<DeviceList>>' cannot be assigned to the type 'Observable<DeviceList>'

// FUNCTION TO RETRIEVE DEVICE LIST fetchDeviceList(): Observable < DeviceList > { this.setToken(); return this.http.get<DeviceList>(this.api_url + '/devices', this.httpOptions1) .retry(2); } I am facing a challenge in this p ...

"Encountering issues with Angular2's FormBuilder and accessing nested object properties,

As I dip my toes into TypeScript and Angular2, I find myself grappling with a nested object structure in an API. My goal is to align my model closely with the API resource. Here's how I've defined the "Inquiry" model in TypeScript: // inquiry.ts ...

Is it possible to obtain a reference that can call an operator?

Is it possible to obtain a reference to an operator (like ===) in TypeScript? The reason behind this question is the following function: function dedup<T>(values: T[], equals: (a: T, b: T) => boolean): T[] { return values.reduce<T[]>((pre ...

The swipe motion will be executed two times

By pressing the Circle button, the Box will shift to the right and vanish from view. Further details (FW/tool version, etc.) react scss Typescript framer-motion import "./style.scss"; import React, { FunctionComponent, useState } from &q ...

Change a nullable string property within an interface to a non-nullable string property

Looking at two interfaces, one with a nullable vin and the other without: interface IVehicle { vin: string | null; model: string; } interface IVehicleNonNullVin { vin: string; model: string; } The goal is to convert a model from IVehicle ...

Retrieve the row from the table using the "let" keyword

I'm attempting to retrieve the row of a table by selecting a radio button. My goal is to identify the selected row in order to access the experiment ID, but when I try this, the alert shows "Row index is: undefined." I found the code at: https://www. ...

TypeScript error TS2503: Namespace 'browser' not found

I encountered a problem while trying to compile TypeScript, resulting in the following error related to @cliqz/adblocker: node_modules/@cliqz/adblocker/dist/commonjs/request.d.ts:12:37 - error TS2503: Cannot find namespace 'browser'. 12 export t ...

Is there a way to manually toggle the Admin LTE 3 sidebar in Angular 12?

My project is using the Admin LTE 3 theme and I have set up the layout.html as follows: <div class="wrapper w-100"> <!-- Navbar --> <app-header></app-header> <!-- /.navbar --> <!-- Main Sidebar Container --> &l ...

Unable to employ a custom Typescript .d.ts file

Currently, I am delving into learning TypeScript and encountering a hurdle while attempting to define a class in a TypeScript definition file and then utilize it in a TypeScript file. The dilemma lies with a JavaScript "class" called "Facade," which serve ...

Experiencing issues with effectively using a component

I have created a component, but I am unable to use it. This is a problem I have never faced before. https://i.sstatic.net/n5I8V.png Despite extensive Google searches, I have not been able to find a solution. For reference, you can view an example of the ...

Learn the process of extracting an array of objects by utilizing an interface

Working with an array of objects containing a large amount of data can be challenging. Here's an example dataset with multiple key-value pairs: [{ "id": 1, "name":"name1", age: 11, "skl": {"name": & ...

Tips for organizing an array into three separate objects based on specific criteria

I have taken a word and split it into an array. Now, I am trying to divide that array into 3 separate objects like this: Input: var input = "7RD FLOOR, PAVE AVENUE BUILDING, DUNG STREET, 48 JUNG-GU, SEOUL 100-203" Desired Output: let addresses = { ad ...

billboard.js: The 'axis.x.type' property is conflicting with different data types in this context

axis: { x: { type: "category" } }, An issue has arisen: The different types of 'axis.x.type' are not compatible with each other. The value of 'string' cannot be assigned to '"category" | &qu ...

Dynamically incorporate new methods into a class

Currently, I am in the process of implementing setters and getters for items that will be stored in session storage. These methods are being written within a service. However, upon attempting to call these functions in my component, I am encountering a tra ...

What is the best way to customize the hover-over label appearance for a time series in ChartJS?

In my Angular 8 setup, I am working with the 'Date' data type and configuring multiple datasets of a 'Cartesian' type. I have been referring to documentation from here for guidance. Despite setting up the X Axis using callbacks in my co ...