Question
I have a base class called Entity that is extended by many subclasses such as Customer, Product, and ProductCategory.
I want to dynamically clone an object graph in TypeScript. For example, a Customer contains multiple Product objects, and each Product may contain a ProductCategory.
Here is a simplified example:
const cust: Customer = new Customer();
cust.name = "someName";
cust.products.push(new Product(someId1));
cust.products.push(new Product(someId2));
To clone the whole tree of objects, I created a clone() method in Entity:
public clone(): any {
const cloneObj = new this.constructor();
for (const attribute in this) {
if (typeof this[attribute] === "object") {
cloneObj[attribute] = this.clone();
} else {
cloneObj[attribute] = this[attribute];
}
}
return cloneObj;
}
When TypeScript is transpiled, it raises this error:
error TS2351: Cannot use 'new' with an expression whose type lacks a call or construct signature.
The script still works at runtime, but I would like to remove the TypeScript error and use a safer cloning approach.
Short Answer
By the end of this page, you will understand why new this.constructor() causes a TypeScript error, how cloning class instances differs from copying plain objects, and how to implement safer shallow and deep clone patterns for class-based models in TypeScript.
Concept
In TypeScript, cloning an object is not always as simple as copying its properties. The main reason is that class instances are more than plain objects.
A class instance can contain:
- Properties
- Methods inherited from the prototype
- Nested class instances
- Arrays of class instances
- Special initialization logic in the constructor
The error in your example happens because TypeScript does not know that this.constructor is definitely a constructable class type. At runtime, JavaScript allows it, but TypeScript's type system requires stronger guarantees.
There are really two separate topics here:
- How to create a new instance of the same class
- How to copy nested objects correctly
Your current code tries to solve both at once, but it has a few issues:
this.constructoris not typed as a class constructorthis.clone()inside the loop clones the whole object repeatedly instead of cloning the current propertytypeof value === "object"is too broad because it includes arrays andnull- Deep cloning class instances requires care if you want to preserve the correct class types
Why this matters in real programming:
- You may want to duplicate data before editing it in a form
- You may need immutable updates in state management
- You may need to create copies of domain models without sharing references
- You may want to avoid accidental mutation of nested data
The key idea is: copying object graphs safely requires both type awareness and a clear cloning strategy.
Mental Model
Think of a class instance like a house blueprint plus furniture.
- The class is the blueprint
- The instance properties are the furniture inside the house
- A shallow copy builds a new house but reuses the same furniture references
- A deep copy builds a new house and also duplicates the furniture inside it
Now imagine saying: "Build me another house using whatever blueprint this house came from." That is what new this.constructor() is trying to do.
JavaScript says, "Fine, I can try." TypeScript says, "Prove to me that this blueprint is actually constructable."
That is why the code runs in JavaScript but causes a TypeScript error.
Syntax and Examples
A common beginner-friendly solution is to separate instance creation from property copying.
Example 1: Shallow clone with Object.assign
class Entity {
clone<T extends this>(): T {
return Object.assign(
Object.create(Object.getPrototypeOf(this)),
this
);
}
}
What this does
Object.getPrototypeOf(this)keeps the same prototypeObject.create(...)creates a new object of the same runtime typeObject.assign(...)copies properties onto it
This is a shallow clone.
If a Customer has a products array, both the original and cloned customer will still reference the same array.
Example 2: Simple deep clone for nested entities
class Entity {
clone<T extends this>(): T {
const cloneObj = Object.create(Object.getPrototypeOf(this));
for (const key in this) {
const value = this[key as keyof this];
cloneObj[key] = this.cloneValue(value);
}
return cloneObj;
}
private cloneValue(value: any): any {
if (value === null || value === undefined) {
return value;
}
if (Array.isArray(value)) {
return value.map(item => this.cloneValue(item));
}
if (value instanceof Entity) {
return value.clone();
}
if (typeof value === "object") {
return { ...value };
}
return value;
}
}
Why this is better
- It avoids
new this.constructor() - It handles arrays separately
- It clones nested
Entityobjects recursively - It does not accidentally call
this.clone()for every property
Example 3: Using it with subclasses
class ProductCategory extends Entity {
constructor(public name: string) {
super();
}
}
class Product extends Entity {
category?: ProductCategory;
constructor(public id: number) {
super();
}
}
class Customer extends Entity {
name = "";
products: Product[] = [];
}
const customer = new Customer();
customer.name = "Alice";
const p1 = new Product(1);
p1.category = new ProductCategory("Books");
customer.products.push(p1);
const copy = customer.clone();
This produces a new Customer instance with cloned nested Product and ProductCategory instances.
Step by Step Execution
Consider this code:
class Entity {
clone<T extends this>(): T {
const cloneObj = Object.create(Object.getPrototypeOf(this));
for (const key in this) {
const value = this[key as keyof this];
cloneObj[key] = this.cloneValue(value);
}
return cloneObj;
}
private cloneValue(value: any): any {
if (value === null || value === undefined) return value;
if (Array.isArray(value)) return value.map(item => this.cloneValue(item));
if (value instanceof Entity) return value.clone();
if (typeof value === "object") return { ...value };
return value;
}
}
Now suppose we run:
const customer = new Customer();
customer.name = "Alice";
customer.products.push(new Product(1));
const cloned = customer.clone();
Step by step
customer.clone()is called.Object.getPrototypeOf(this)gets the prototype ofcustomer, which isCustomer.prototype.Object.create(...)creates a new empty object with the same prototype.- The
for...inloop goes through enumerable properties oncustomer. - For
name, the value is a string, socloneValue()returns the same string. - For
products, the value is an array, socloneValue()maps over the array. - Each
Productin the array is anEntity, soproduct.clone()is called recursively. - Each nested object is copied into the new structure.
- The result is a new
Customerinstance with its ownproductsarray and clonedProductinstances.
Important detail
Primitive values such as strings, numbers, and booleans are copied by value. Objects and arrays must be copied explicitly, or the clone will share references with the original.
Real World Use Cases
Cloning class objects is useful in many common situations.
Form editing
You load a Customer from the server and let a user edit it in a form. To avoid changing the original object until the user clicks Save, you clone it first.
Undo or cancel changes
Applications often keep an editable copy and restore the original if the user cancels.
State management
In front-end apps, direct mutation can cause bugs. Cloning helps create new state objects safely.
Data transformation
You may clone an API model before enriching it with calculated fields or temporary UI-specific fields.
Testing
Tests often clone sample data so each test starts with a clean independent object graph.
Real Codebase Usage
In real projects, developers usually avoid a single magical clone method for every case unless the object model is simple and well controlled.
Common patterns include:
Explicit clone methods per class
class Product extends Entity {
constructor(public id: number, public name: string) {
super();
}
clone(): Product {
return new Product(this.id, this.name);
}
}
This is the safest option when constructors matter.
Factory methods
Instead of cloning through constructors directly, teams use methods like:
fromJson()toJson()copy()clone()
These make object creation predictable.
Guard clauses during cloning
Developers usually check for:
null- arrays
- dates
- nested entities
- optional properties
Validation before copying
Some codebases validate the object shape before cloning, especially when data comes from APIs.
Prefer plain data when possible
In many apps, data models are kept as plain objects rather than rich class instances. This makes copying easier with spread syntax or dedicated utilities.
Avoid cloning everything blindly
A real object graph may contain:
- circular references
- functions
- DOM objects
- class instances with private state
That is why production code often uses targeted cloning rather than generic recursion.
Common Mistakes
1. Cloning the whole object instead of the current property
Your original code does this:
if (typeof this[attribute] === "object") {
cloneObj[attribute] = this.clone();
}
This clones this again, not this[attribute].
Why it is a problem
- It can create incorrect recursive structures
- It may lead to infinite recursion
2. Using typeof value === "object" without checking for null
if (typeof value === "object") {
// this also matches null
}
Fix
if (value !== null && typeof value === "object") {
// safe object check
}
3. Forgetting that arrays are objects
if (typeof value === "object") {
cloneObj[key] = { ...value }; // wrong for arrays
}
Fix
Handle arrays first:
if (Array.isArray(value)) {
return value.map(item => this.cloneValue(item));
}
4. Expecting JSON cloning to preserve classes
const copy = JSON.parse(JSON.stringify(customer));
This creates a plain object, not a Customer instance.
Problems
- methods are lost
- prototypes are lost
- dates become strings
5. Assuming constructors always take no arguments
const cloneObj = new this.constructor();
If a subclass constructor requires parameters, this may fail or create invalid objects.
6. Ignoring circular references
If A references B and B references A, naive recursion will crash with a stack overflow.
Avoidance
Use a Map of already-cloned objects for advanced deep cloning scenarios.
Comparisons
| Approach | Keeps class prototype? | Deep clone? | Handles nested class instances? | Notes |
|---|---|---|---|---|
Spread syntax { ...obj } | No for class behavior in practice | No | No | Good for plain objects |
Object.assign into new instance/prototype | Yes | No | No | Good for shallow class copy |
JSON.parse(JSON.stringify(obj)) | No | Yes, for plain JSON data | No | Loses methods, dates, prototypes |
Custom clone() method | Yes | Can be | Can be | Best when you control the classes |
structuredClone() | No class methods/prototype preservation | Yes for supported data | Not as class instances | Good for plain structured data |
Shallow clone vs deep clone
| Type | What gets copied? | Shared nested references? | Use when |
|---|---|---|---|
| Shallow clone | Top-level properties only | Yes | Nested data is safe to share |
| Deep clone | Entire object graph | No | Nested data must be independent |
new this.constructor() vs Object.create(Object.getPrototypeOf(this))
| Technique | TypeScript-friendly? | Constructor runs? | Best for |
|---|---|---|---|
new this.constructor() | Usually no without typing work | Yes | Rare cases with properly typed constructors |
Object.create(Object.getPrototypeOf(this)) | Yes | No | Prototype-preserving copies without constructor issues |
Cheat Sheet
Quick rules
- Use a shallow clone when only top-level properties need copying.
- Use a deep clone when nested objects or arrays must be independent.
- Avoid
new this.constructor()unless you explicitly type the constructor. typeof value === "object"includes arrays andnull.- JSON cloning does not preserve class instances.
Safe shallow clone for class instances
clone<T extends this>(): T {
return Object.assign(
Object.create(Object.getPrototypeOf(this)),
this
);
}
Basic deep clone pattern
private cloneValue(value: any): any {
if (value === null || value === undefined) return value;
if (Array.isArray(value)) return value.map(item => this.cloneValue(item));
if (value instanceof Entity) return value.clone();
if (typeof value === "object") return { ...value };
return value;
}
Warning signs
- Constructor requires parameters
- Object contains circular references
- Object contains
Date,Map,Set, or functions - Object is not a plain object or controlled class instance
Best practice
If each class has special rules, write an explicit clone() method for that class.
FAQ
Why does new this.constructor() cause a TypeScript error?
Because TypeScript does not know that this.constructor is definitely a constructable type with a valid new signature.
Why does the code still work in JavaScript?
JavaScript checks this at runtime, not compile time. TypeScript is warning that the type is not safe enough.
Is Object.assign enough for cloning?
Only for shallow cloning. Nested arrays and objects will still be shared.
Can I use JSON.parse(JSON.stringify(obj)) to deep clone?
Only for simple JSON-like data. It removes class prototypes, methods, and special object types.
How do I clone nested class instances?
Use a custom recursive clone() method and handle arrays, nested entities, and plain objects separately.
Should every class have its own clone() method?
Often yes. It is usually the safest and clearest solution when constructors or nested fields differ.
What if my objects contain circular references?
A naive recursive clone will fail. You need a more advanced approach that tracks already-cloned objects with a Map.
Mini Project
Description
Build a small TypeScript model system with Entity, Customer, Product, and ProductCategory, then implement cloning so editing a copied customer does not affect the original. This demonstrates prototype-preserving object cloning and recursive copying of nested entities.
Goal
Create a deep clone of a Customer instance so nested products and categories can be modified independently from the original object.
Requirements
- Create an
Entitybase class with a reusableclone()method. - Add
Customer,Product, andProductCategoryclasses that extendEntity. - Ensure arrays of nested entities are cloned deeply.
- Show that changing the clone does not change the original.
- Keep the solution valid TypeScript without using
new this.constructor().
Keep learning
Related questions
@Directive vs @Component in Angular: Differences, Use Cases, and When to Use Each
Learn the difference between @Directive and @Component in Angular, including use cases, examples, and when to choose each.
Accessing Input Value from EventTarget in TypeScript
Learn why EventTarget has no value property in TypeScript and safely read values from HTML input events in Angular applications.
Angular (change) vs (ngModelChange): What’s the Difference?
Learn the difference between Angular (change) and (ngModelChange), when each fires, and which one to use in forms and inputs.