Search code examples
typescriptnamed-parameters

Typescript: Create class via constructor passing in named parameters?


I have a class where I have the constructor defined with 3 parameters which are all optional. I was hoping to be able to pass in named parameters so I don't need to pass in undefined.

constructor(year?: number,
            month?: number,
            date?: number)

I was hoping to create an intance of the class like so

  const recurrenceRule = new MyNewClass(month: 6)

but it didn't work and i tried

  const recurrenceRule = new MyNewClass(month = 6)

and that didn't work.

The only way i got it to work was either

  const recurrenceRule = new MyNewClass(undefined, 4)

or

  const recurrenceRule = new MyNewClass(, 4)

But it seems so messy, I was hoping to pass in named arguments and becasue they are all optional I should be able to just pass in 1 - right ?


Solution

  • You can use Object destructuring introduced in ES6 to achieve the desired behavior: reference. TypeScript is able to transpile this feature for usage with ES5 to target older browsers. However, as of ES6, this is also perfectly valid JavaScript.

    Basically, it looks like this: constructor({ year, month, day}) and is invoked, for instance, as new Bar({ year: 2017 }). Then you can access year as a variable within the constructor, e.g. for assigning this.year = year.

    More interesting than that is the usage with default values, e.g.

    constructor({ year = new Date().getFullYear(), 
                  month = new Date().getMonth(), 
                  day = new Date().getDay()
                } = {})
    

    which allows invoking the constructor with 0, 1, 2 or 3 parameters respectively (see snippet below).

    The somewhat cryptic = {} is for the case when you create a new instance without any parameters. First, {} is used as the default value for the parameter object. Then, since year is missing, the default value for that one is added, then for month and for day respectively.

    For usage with TypeScript, you can, of course, add additional typings,

    constructor({ year = new Date().getFullYear(),
                  month = new Date().getMonth(),
                  day = new Date().getDay()
    }: { year?: number, month?: number, day?: number } = {}) { 
        ...                
    }
    

    Although this really looks cryptic.

    class Bar {
      constructor({ year, month, day }) {
        this.year = year;
        this.month = month;
        this.day = day;
      }
      
      log () {
        console.log(`year: ${this.year}, month: ${this.month}, day: ${this.day}`);
      }
    }
    
    new Bar({ day: 2017 }).log();
    
    class Foo {
      constructor({ year = new Date().getFullYear(), 
                    month = new Date().getMonth(), 
                    day = new Date().getDay()
                  } = {}) {
        this.year = year;
        this.month = month;
        this.day = day;
      }
      
      log () {
        console.log(`year: ${this.year}, month: ${this.month}, day: ${this.day}`);
      }
    }
    
    console.log('with default value:');
    new Foo().log();
    new Foo({ day: 2 }).log();
    new Foo({ day: 2, month: 8 }).log();
    new Foo({ year: 2015 }).log();