Better Constructor Overloading In Typescript/Angular

If you’re coming from a language such as Java or C#, the concept of constructor overloading is a pretty common one. But as a refresher in say C#, we can overload constructors like so :

class MyClass
{
	public MyClass(string value)
	{
		this.value = value;
	}
	
	public MyClass(int value)
	{
		this.value = value.toString();
	}

	private string value;
}

Whereby we create two different constructors, that take two very different parameters, to construct our object. It should also note that in C#, there is no limitation on how different these constructors can be. For example you can even have a different number of parameters :

class MyClass
{
	public MyClass(string value)
	{
		this.value = value;
	}
	
	public MyClass(string first, string second)
	{
		this.value = first + second;
	}

	private string value;
}

In Typescript/Angular, things aren’t so easy. If we tried the above code in Typescript such as :

export class MyClass
{
	constructor(value : string)
	{
		this.value = value;
	}
	
	constructor(value : number)
	{
		this.value = value.toString();
	}

	private value : string;
}

You will get the following error :

Module parse failed: Duplicate constructor in the same class 

You’ll probably also see something like :

error TS2392: Multiple constructor implementations are not allowed.

But you’ve heard that Typescript *does* support constructor overloading, so what gives? Why doesn’t this work.

Overloading Constructors In Typescript

Overloading in Typescript is a little different to what you might expect, while you can overload constructors, you cannot have multiple implementations. What this means in practice is that you either can create a union type such as :

export class MyClass
{
	constructor(value : string | number)
	{
		this.value = value.toString();
	}

	private value : string;
}

Where the constructor can accept one of either types, and you handle both within the same implementation. In this example we can handle both easily because calling toString() on a string is a safe operation, but otherwise you have to resort to using typeof checks :

export class MyClass
{
	constructor(value : string | number)
	{
		if(typeof value === "string")
		{
			this.value = value;
		}

		if(typeof value === "number")
		{
			this.value = value.toString();
		}
	}

	private value : string;
}

Honestly for me, this just looks like a mess and is hardly worth the hassle.

It get worse (just in my opinion), when you want a differing amount of parameters for two different constructors. What you need to instead do is create a single implementation that each constructor can “fall down” to the next without breaking. For example :

export class MyClass
{
    constructor(value : string)
    constructor(first : string, second? : string)
    {
        if(!second)
        {
            this.value = first;
        }else 
        {
            this.value = first + second;
        }
    }

    private value : string;
}

The constructor with a single “value” is able to fall down to the second constructor because the second parameter is nullable. We can determine which constructor was used by checking if the second value is set or not.

Again, less than ideal. I find the biggest issue is as you add constructors, removing one in the “middle” of the list suddenly breaks everything. In my opinion, this way of overloading constructors in typescript is extremely brittle and prone to giving developers headaches.

A Better Way With Static Factories

Because of the way constructor overloading works, it can be hard to wrangle if you have wildly different constructor implementations depending on the parameters. For this reason I use a simpleĀ  static factory pattern.

export class MyClass
{
    static fromSingleValue(value : string) : MyClass {
        var result = new MyClass();
        result.value = value;
        return result;
    }

    static fromTwoValues(first : string, second : string) : MyClass {
        var result = new MyClass();
        result.value = first + second;
        return result;
    }

    private value : string;
}

Now I can just call MyClass.fromSingleValue(‘something’) to get a class constructed for me. Better yet, any developer looking at this will find it extremely easy to follow, even if they’ve never used Typescript before.

Where static factories really come into their own is when you are consuming an entire other object :

export class MyOtherClass
{
    public value : string;
}

export class MyClass
{
    static fromSingleValue(value : string) : MyClass {
        var result = new MyClass();
        result.value = value;
        return result;
    }

    static fromTwoValues(first : string, second : string) : MyClass {
        var result = new MyClass();
        result.value = first + second;
        return result;
    }

    static fromMyOtherClass(myOtherClass : MyOtherClass) : MyClass {
        var result = new MyClass();
        result.value = myOtherClass.value;
        return result;
    }

    private value : string;
}

Now adding and removing constructors does not break any other constructor, and they all act independently from one another.

Leave a Reply

Your email address will not be published. Required fields are marked *