Miguel Moura
Miguel Moura

Reputation: 39514

Class constructors in TypeScript

I have the following C# class:

public class Envelope<T> {

  public List<Error> Errors { get; private set; } = new List<Error>();

  public Paging Paging { get; private set; }

  public List<T> Result { get; private set; } = new List<T>();


  public Envelope(T result) : this(new List<T> { result }, null, new List<Error>()) { }

  public Envelope(List<T> result) : this (result, null, new List<Error>()) { }

  public Envelope(List<Error> errors) : this(new List<T>(), null, errors) { }

  public Envelope(List<T> result, Paging paging, List<Error> errors) {

    Errors = errors;
    Paging = paging;
    Result = result;

  }

}

I need to convert this class to TypeScript on an Angular 6 project so I did:

export class Envelope<T> {

  errors: Error[];
  paging: Paging;
  result: T[];

  constructor(result: T[], paging: Paging, errors: Error[]) {

    this.errors = errors;
    this.paging = paging;
    this.result = result;

  }

}

The problem is that Typescript does not allow more than one constructor so replicating the quite different constructors I have in C# seems impossible.

Is there a way to do this?

Should Envelope be an interface in TypeScript?

Basically Envelope is a Wrapper for an API response to contain various objecs such as the Result itself, Paging and List of possible errors.

Upvotes: 0

Views: 1251

Answers (2)

user4676340
user4676340

Reputation:

Given your code, this should work and seems waaaay simpler :

export class Envelope<T> {
  constructor(
    public result?: T[], 
    public paging?: Paging, 
    public errors: Error[] = null
  ) {}
}

You have shorthands :

  • an access modifier appended before a constructor parameter will declare a class member. So you don't need to instanciate your class members in your constructor.
  • Using a ? after a member name renders it optional, meaning that if you don't provide it, you'll simply have undefined in place of it's value.
  • Setting a value in the parameters of a function (or a constructor) renders the member optional and gives it a default value.

As a side note, Typescript allow several constructors in a ... strange way :

export class Envelope<T> {
  constructor(public result: T[])
  constructor(public paging: Paging)
  constructor(public errors: Error[])
  // ...
  {}
}

The issue with this syntax is that you will have to test every paramter with instanceof to see if they're of the expected type, which I don't particularily like (optional parameters seems simpler to me)

Upvotes: 3

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 250366

You can create constructor overloads, but you have to distinguish between them manually in the implementation. In your case, the first argument for the implementation would be a union of T| T[] | Error[] and you can use type guards to manually differentiate between the cases in the union:

function isErrorArray<T>(e: T | Error[]): e is Error[] {
    return e instanceof Array && e[0] && e[0] instanceof Error;
}
export class Envelope<T> {

    errors: Error[];
    paging: Paging;
    result: T[];

    constructor(result: T)
    constructor(errors: Error[])
    constructor(result: T[], paging: Paging, errors: Error[])
    constructor(result: T | T[] | Error[], paging?: Paging, errors?: Error[]) {
        if (isErrorArray(result)) {
            errors = result;
            result = [] as T[];
        }
        if (Array.isArray(result)) {

        } else {
            result = [result];
        }
        this.errors = errors;
        this.paging = paging;
        this.result = result; // result will be T[] because of the above ifs

    }

}

Upvotes: 4

Related Questions