Youcef LAIDANI
Youcef LAIDANI

Reputation: 59950

TypeScript: Multiple constructor implementations are not allowed

I have an Object where I use it in multiple services, and each one should take some parameters, so I create two constructors, but TypeScript did not allow me to do this. My example is:

class User {
    id: number;
    username: string;
    password: string;
    email: string;
    firstName: string;
    lastName: string;
    roles: string[];

    constructor(username: string, password: string){
        this.username = username;
        this.password = password;
    }

    constructor(id: number, username: string, firstname: string, lastname: string, roles: string[]){
        this.id = id;
        this.username= username;
        this.firstname= firstname;
        this.lastname= lastname;
        this.roles = roles;
    }
    //.. and maybe another constructor also
}

Is there a trick to solve this issue, please?


When I use the optional ? in constructors for example:

constructor(
    public id?: number,
    public username?: string,
    public email?: string,
    public password?: string,
    public firstName?: string,
    public lastName?: string,
    public roles?: string[]) {
}

and when I get my data from backend:

this.service.usersList().subscribe(users => {
  console.log(users);
  this.dataSource.data = users;
});

The roles is set in the password and not in the roles failed:

{
  "id": 1,
  "username": "user1",
  "email": "[email protected]",
  "password": [
    "USER",
    "MODR"
  ]
}

For that I'm not sure about this trick.


Maybe I was not precise, but I use this method to parse my data:

static fromJson(item: Object): any {
    return new User(
        item['id'],
        item['username'],
        item['email'],
        item['roles']
    );
}

For that, when I create a constructor with optional, it will set the attributes in order of my call.

Upvotes: 31

Views: 45597

Answers (6)

Tabares
Tabares

Reputation: 4335

JavaScript does not support multiple constructors in the same way that some other object-oriented languages like Java or C# do. This is because JavaScript classes can only have one constructor method. However, you can achieve similar functionality by using default parameters, conditional logic, or factory methods within a single constructor. Here are some ways to handle multiple constructor scenarios in JavaScript:

class Car {
  private model: string;
  private year: number;

  // One constructor per class
  // Parameterized constructor 
  constructor(model, year) {
    this.model = model;
    this.year = year;
  }

  // Using factory methods
  // Default constructor
  static createWithDefault() {
    return new Car("Unknown", 0);
  }

  // Another parameterized constructor
  static createWithModel(model) {
    return new Car(model, 2025); // Default year
  }

  displayDetails() {
    console.log(`Model: ${this.model}, Year: ${this.year}`);
  }
}

// Creating objects using factory methods
let car1 = Car.createWithDefault();
let car2 = Car.createWithModel("Honda");
let car3 = new Car("Toyota", 2022);

// Displaying details of each car
car1.displayDetails(); // Output: Model: Unknown, Year: 0
car2.displayDetails(); // Output: Model: Honda, Year: 2025
car3.displayDetails(); // 

Conditional logic:

class Car {
    constructor(model, year) {
        if (typeof model === "undefined" && typeof year === "undefined") {
            this.model = "Unknown";
            this.year = 0;
        } else if (typeof year === "undefined") {
            this.model = model;
            this.year = 2025; // Default year
        } else {
            this.model = model;
            this.year = year;
        }
    }

    displayDetails() {
        console.log(`Model: ${this.model}, Year: ${this.year}`);
    }
}

// Creating objects using different constructor scenarios
let car1 = new Car();
let car2 = new Car("Honda");
let car3 = new Car("Toyota", 2022);

// Displaying details of each car
car1.displayDetails(); // Output: Model: Unknown, Year: 0
car2.displayDetails(); // Output: Model: Honda, Year: 2025
car3.displayDetails(); // Output: Model: Toyota, Year: 2022

Upvotes: 0

velocity
velocity

Reputation: 2066

I had the same error while i was trying to override the default constructor to populate the fields from json data, coming from the Java world i thought this was possible with Typescript, but apparently it is not. An idea that i had is to define a static method fromData() to create a new instance, set the values from the json object and return it, example:

public static fromData(data: any): Account {
  let newInstance = new Account();
  newInstance.id = data.id
  ...
  return newInstance;
}

then in the observable i do:

subscribe({
  next: (data:any) => {
    this.account = Account.fromData(data)
  },
  error: (error:any) => console.log("Error "+error);
})

Upvotes: 0

Chris Vilches
Chris Vilches

Reputation: 1187

Here's a type-safe way to create multiple constructors using interfaces and discriminating unions.

It's a bit more verbose than other solutions, but it's 100% type-safe. It doesn't need to set any value to optional.

interface ShortConstructor {
  type: 'short'
  username: string
  password: string
}

interface LongConstructor {
  type: 'long'
  id: number
  username: string
  password: string
  firstName: string
  lastName: string
  roles: string[]
}

type UserConstructor = ShortConstructor | LongConstructor

class User {
  id: number
  username: string
  password: string
  email: string
  firstName: string
  lastName: string
  roles: string[]

  constructor (user: UserConstructor) {
    if (user.type === 'short') {
      this.shortConstructor(user)
    } else {
      this.longConstructor(user)
    }
  }

  shortConstructor ({ username, password }: ShortConstructor): void {
    this.username = username
    this.password = password
  }

  longConstructor ({ id, username, firstName, lastName, roles }: LongConstructor): void {
    this.id = id
    this.username = username
    this.firstName = firstName
    this.lastName = lastName
    this.roles = roles
  }
}

console.log(new User({ type: 'short', username: 'Chris', password: 'abcdef' }))
console.log(new User({
  type: 'long',
  id: 123,
  username: 'Chris',
  password: 'abcdef',
  firstName: 'AAAA',
  lastName: 'BBBB',
  roles: ['developer', 'manager']
}))

Output:

User { username: 'Chris', password: 'abcdef' }
User {
  id: 123,
  username: 'Chris',
  firstName: 'AAAA',
  lastName: 'BBBB',
  roles: [ 'developer', 'manager' ]
}

Upvotes: 0

Faradox
Faradox

Reputation: 179

Use:

export class A {
   constructor() {
      // Something here
   }
   secondConstructor() {
      // Something here
      return this;
   }
}

And then you use just like this:

const a = new A();
const a2 = new A().secondConstructor();

Upvotes: -8

Youcef LAIDANI
Youcef LAIDANI

Reputation: 59950

I found the solution:

What happened?

When you create a constructor with optional parameters and try to call this constructor, it will set the attributes with the order of call. For that, when I call:

new User(
    item['id'],
    item['username'],
    item['email'],
    item['roles']
);

The roles in set in the firstName or password.

Solution

To solve this, it's required to change the order or parameters in the constructor:

constructor(
    public id?: number,
    public username?: string,
    public email?: string,
    public roles?: string[],

    public password?: string,
    public firstName?: string,
    public lastName?: string) {
}

Or if you won't change the order, just use undefined for example:

new User(
    item['id'],
    item['username'],
    undefined,
    item['email'],
    undefined,
    undefined,
    item['roles']
);

Until you arrive to the position of your attribute.

Upvotes: 1

Aymen TAGHLISSIA
Aymen TAGHLISSIA

Reputation: 1915

You can't use multiple constructors, but you can add a few optional parameters and verify if it exists, like the following:

class User {
    id: number;
    username: string;
    password: string;
    email: string;
    firstname: string;
    lastname: string;
    roles: string[];
    // The "?" says that its optional parameter
    constructor(id?: number, username?: string, firstname?: string,
        lastname?: string, roles?: string[], password?: string) {
        if (id) { // if id exists , you can implement the first constructor
            this.id = id;
            this.username = username;
            this.firstname = firstname;
            this.lastname = lastname;
            this.roles = roles;
        }
        if (password) { // if password exists : you can implement the second one
            this.username = username;
            this.password = password;
        }
    }
}

Your response should be like this before this works fine:

static fromJson(item: Object): any {
    return new User({
        id : item['id'],
        username : item['username'],
        email : item['email'],
        roles : item['roles']
    });
}

So your constructor should be like this:

constructor(user: any){
    if (user.id) { // if id exists , you can implement the first constructor
        this.id = user.id;
        this.username = user.username;
        this.firstname = user.firstname;
        this.lastname = user.lastname;
        this.roles = user.roles;
    }
    if (user.password) { // if password exists : you can implement the second one
        this.username = user.username;
        this.password = user.password;
    }
}

Or if you don't want to do that, you can set the response regarding the order, like this:

static fromJson(item: Object): any {
    return new User(
        item['id'],
        item['username'],
        undefined,
        item['email'],
        undefined,
        undefined,
        item['roles']
    );
}

Upvotes: 35

Related Questions