11

There is a common pattern in JavaScript whereby a "constructor" accepts an optional options object. Additionally, that options object may contain only the options the caller wishes to override. For example:

function Foo(options) {
  this._options = {
    foo: 'bar',
    answer: 42,
    amethod: function(){}
  };
  this._options = Object.assign(this._options, options);
}

let foo1 = new Foo();
foo2._options.foo; // 'bar'
foo1._options.answer; // 42
foo1._options.amethod(); // undefined

let foo2 = new Foo({answer: 0, amethod: function(a) { return a; }});
foo2._options.foo; // 'bar'
foo2._options.answer; // 0
foo2._options.amethod('foo'); // 'foo'

Is it possible to implement this pattern in Typescript? If so, how?

3 Answers 3

16

Seems as though you could do this with an Interface with optional members, and using Object.assign as you already have done:

interface Options {
  foo?: string;
  answer?: number,
  aMethod?: (a:string) => string;
}

class Foo {
    options: Options;
    constructor(options:Options) {
        this.options = {
            foo: 'bar',
            answer: 42,
            aMethod: function(){}
        };
        Object.assign(this.options, options);
    }
}
var foo1 = new Foo({});
foo1.options.foo; // 'bar'
foo1.options.answer; // 42
foo1.options.aMethod; // function()
var foo2 = new Foo({answer: 0, aMethod: function(a:string) { return a; } );
foo1.options.foo; // 'bar'
foo1.options.answer; // 0
foo1.options.aMethod; // function(a)

TS Playground Example

Sign up to request clarification or add additional context in comments.

1 Comment

I see. I was trying to define the method as a fully typed method and optional a the same time. TS was having none of that. Thank you.
12

In Typescript

Let me chime in with a TS answer. In TS, you can of course define the type of the options object, but as of TS 3.2 you can't assign "partial defaults" in the function parameters (i.e. when unset properties of the object will be defaulted to some value).

So this is a real example of a web crawler function, using destructuring. You could also use Object.assign but then for type safety you would have to separately define the type interface for options.

All in all, the only caveat is that you will have to mention those properties twice which will be assigned a default value.

async downloadPageHTML(url: string, options: {
    cookies?: puppeteer.Cookie[],
    launchOpts?: LaunchOptions,
    pageLoadOpts?: Partial<puppeteer.NavigationOptions>,
    userAgent?: string
    browser?: puppeteer.Browser
} = {}) {
    let {
        pageLoadOpts = {},
        launchOpts = {},
        cookies = [],
        userAgent = getUserAgent(url)
    } = options;
    // ...
}

1 Comment

please name my daughter. you rock
5

Here's a more exotic ES6 way to implement default options with destructuring assignment instead of Object.assign():

interface Options {
  foo?: string;
  answer?: number,
  aMethod?: (a:string) => string;
}

class Foo {
    options: Options;
    constructor({
            foo = 'bar',
            answer = 42,
            aMethod = undefined
        }: Options = {}) {
        this.options = { foo, answer, aMethod };
    }
}
var foo1 = new Foo();
console.log("foo1: foo", foo1.options.foo); // 'bar'
console.log("foo1: answer", foo1.options.answer); // 42
console.log("foo1: aMethod", foo1.options.aMethod); // function()
var foo2 = new Foo({answer: 0, aMethod: (a:string) => { return a; } );
console.log("foo2: foo", foo2.options.foo); // 'bar'
console.log("foo2: answer", foo2.options.answer); // 0
console.log("foo2: aMethod", foo2.options.aMethod); // function(a)

Further explanation: http://simonsmith.io/destructuring-objects-as-function-parameters-in-es6/

4 Comments

Sadly, that isn't supported by Node yet -- github.com/nodejs/node/issues/2823
It's TypeScript. It has to be transpiled anyway before Node executes it. Might as well compile to ES5.
This question was related to an issue I was having while updating a third party library. I'd consider doing an ES5 transpile if it were really my own project, but I'm fond of that to begin with.
For reference, Node supports destructuring behind the flag --harmony_destructuring. Node 6 will support destructuring without the flag, and it should ship within a few weeks. node.green/#destructuring--declarations

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.