0

When instantiating an object I much prefer the following format:

const MyTest = new Test({
  title: 'hello';
});

over

const MyTest = new Test('hello');

especially when there are a lot of properties to pass.

I tried to set this up using the following interface and class definitions:

interface ITest {
  title: string;

  readonly titlePlusCheese: string;
}

class Test implements ITest {
  public title: string;

  constructor(args: ITest) {
    this.title = args.title;
  }

  get titlePlusCheese(): string {
    return `${this.title} CHEESE`;
  }
}

However, when calling const MyTest = new Test({ title: 'hello' }); I get the following error:

Property 'titlePlusCheese' is missing in type '{ title: string; }' but required in type 'ITest'.ts(2345)

However, the following works:

interface ITest {
  title: string;

  readonly titlePlusCheese: string;
}

class Test implements ITest {
  public title: string;

  constructor(title: string) {
    this.title = title;
  }

  get titlePlusCheese(): string {
    return `${this.title} CHEESE`;
  }
}

const MyTest = new Test('hello');

which leads me to suspect I'm doing something silly.

Can anyone shed any light on it?

Joe Czucha
  • 4,123
  • 2
  • 20
  • 28

2 Answers2

2

ITest is the type of just the params you're passing in, not of the class itself. It does not need titlePlusCheese in it. Instead, put this directly in the class and that is all the typing you need. Your getter does this for you and defines titlePlusCheese as an implicitly readonly property of the class because there's no setter. Like this:

interface ITest {
  title: string;
}

class Test {
  public title: string;

  constructor(args: ITest) {
    this.title = args.title;
  }

  get titlePlusCheese(): string {
    return `${this.title} CHEESE`;
  }
}

This should show you why your second version works. It broadly does the same thing as your version implementing ITest, which is to incorporate titlePlusCheese in the class definition rather than the interface.

It is probably worth reading a bit more about the difference between interfaces and classes. I searched and found these useful snippets:

When use a interface or class in Typescript

https://blog.logrocket.com/when-how-use-interfaces-classes-typescript/


Here's a more extended version inspired by Frank's comment to show the difference between using an interface to shape the constructor arguments and another one to specify what the class must implement:

interface IArgs {
  title: string;
}

interface IClass {
  title: string;
  reasonly titlePlusCheese: string
}

class Test implements IClass {
  public title: string;

  constructor(args: IArgs) {
    this.title = args.title;
  }

  get titlePlusCheese(): string {
    return `${this.title} CHEESE`;
  }
}
sifriday
  • 4,342
  • 1
  • 13
  • 24
  • I'm not sure about creating an interface to define the constructor args shape vs creating an interface for define a class shape, first one sounds like incredible weird thing to do, and idiomatically isn't the same. Original ``ITest`` is basically saying, anything that implements this, should provide a proper definition of how to add cheese. – Frank Simon Aug 22 '22 at 15:09
  • That is a fair point and I agree they are not the same thing. I have added an extended example show the difference - what do you think? – sifriday Aug 22 '22 at 16:55
  • Looks great. As I mentioned on my own reply, I don't like that approach for constructor args, but that's OP's taste, and your answer it's *on that point*. Extra for the links and suggestions – Frank Simon Aug 23 '22 at 09:02
0

I'm not sure if I'm following exactly what you are trying to achieve there, but sounds a bit weird, from an OOP perspective, using the interface that defines the shape of a class as the arguments type for the class constructor sounds like an error.

Interface definition could (and usually does) contain methods, abstract properties, etc. that you obviously cannot provide on the construction stage.

That being said, if you really prefer that syntax of new Test({title: 'test'}) (personally I don't, find it confusing and prone to any sort of errors, also isn't going to scale for a bigger number of args), you can either inline the constructor type like constructor({title}: { title: string}) or use something like constructor({ title}: Pick<ITest, "title>), both, far from readable.

Frank Simon
  • 91
  • 1
  • 4