Daniel Kaplan
Daniel Kaplan

Reputation: 67440

How do you implement the options object pattern in typescript?

I'm going to answer my own question here because this is pretty difficult to Google and I want it to be easier for everyone to find.

This article explains what this pattern is:

An “options object” is a programming pattern that you can use to pass any number of named arguments to a function. Rather than using an ordered list of arguments, you simply pass one argument, which is an object containing named keys for all of your options. Keep reading to learn how and why.

You can use typescript features to implement this pattern in a more concise way. See my answer for how.

You can see a similar question about JavaScript here: Implement JavaScript options object parameter pattern?

This question is different because it's about TypeScript. There happens to be an answer on that JavaScript question that explains how to do it the typescript way, but I think it deserves its own question.

Upvotes: 2

Views: 1408

Answers (2)

Daniel Kaplan
Daniel Kaplan

Reputation: 67440

100% Optional

This is how, using destructuring, you can use the options object pattern in typescript (playground link):

    interface Options {
      lastSeparator?: string;
      separatorBetweenArrayOfTwo?: string;
      wrap?: (word: string) => string;
    }
    
    const separatedMessage = (
      words: string[],
      separator: string,
      { 
        lastSeparator = separator, 
        separatorBetweenArrayOfTwo = lastSeparator, 
        wrap = (word: string): string => {
          return word.toString();
        }
       }: Options = {} // the `= {}` at the end lets you skip this arguments completely. e.g., `separatedMessage(["a"], " ");` will compile. Without `= {}`, you would have to call it like this to compile: `separatedMessage(["a"], " ", {});`
    ): string => {
      let buf = '';
    
      words.forEach((word, index, array) => {
        if (index !== 0) {
          if (array.length === 2) {
            buf += separatorBetweenArrayOfTwo;
          } else if (index === array.length - 1) {
            buf += lastSeparator;
          } else {
            buf += separator;
          }
        }
          buf += wrap(word);    
      });
      return buf;
    };
    
    console.log(separatedMessage(["a", "b", "c", "d"], ", "))
    console.log(separatedMessage(["a", "b"], ", ", {}))
    console.log(separatedMessage(["a", "b"], ", ", {lastSeparator: ", and "}))
    console.log(separatedMessage(["a", "b"], ", ", {lastSeparator: ", and ", separatorBetweenArrayOfTwo: " and "}))

This function lets you pass in an array of words and separate them with characters. You can optionally:

  1. Specify a different separator to use last: , and in a, b, c, and d.
  2. Specify a separator that's used for arrays of exactly two elements: and in a and b but , and in a, b, c, and d.
  3. Pass in a function that wraps each word in a string: ' in 'a', 'b', 'c', and 'd'

Some required properties

Playground link

type Options = {
  r1: string;
  o2?: number;
  o3?: string;
  r4: number;
};

foo('qux', {
  r1: 'baz',
  r4: 3,
});

function foo(r0: string, { r1, o2 = 6, o3 = 'bar', r4 }: Options) {
  console.log(`r0=${r0} r1=${r1} o2=${o2} o3=${o3} r4=${r4}`); // r0=qux r1=baz o2=6 o3=bar r4=3
}

/*
foo('qux', {
    r4: 3,
});

Error: Argument of type '{ r4: number; }' is not assignable to parameter of type 'Options'.
  Property 'r1' is missing in type '{ r4: number; }' but required in type 'Options'.
*/

Upvotes: 1

Kevin Zhang
Kevin Zhang

Reputation: 1092

We can define specific key-value pair or any for interface, then destructing the values.

interface Person {
    name: string;
    age: number;
    gender: string;
    hobby?: string;
    [key: string]: any;
}

function logPerson(person: Person) {
    console.log(person);
    return person;
}

function logPerson1(person: { [key: string]: any }) {
    console.log(person);
}

console.clear();
logPerson({ name: 'Kevin', age: 33, gender: 'male', hobby: 'basketball', hah: 'Hah' });
logPerson1({ name: 'Lucy', age: 100, gender: 'female', extra: 'extra property' });

Upvotes: 0

Related Questions