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: .
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
{
clone<T >(): T {
cloneObj = .(.());
( key ) {
value = [key keyof ];
cloneObj[key] = .(value);
}
cloneObj;
}
(: ): {
(value === || value === ) {
value;
}
(.(value)) {
value.( .(item));
}
(value ) {
value.();
}
( value === ) {
{ ...value };
}
value;
}
}
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 ) value.();
( value === ) { ...value };
value;
}
}
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
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
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 |
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)) value.( .(item));
(value ) value.();
( value === ) { ...value };
value;
}
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.
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.
Angular @ViewChild Returning Undefined: Lifecycle, Child Components, and Fixes
Learn why Angular @ViewChild can be undefined, when it becomes available, and how to access child components correctly using lifecycle hooks.