2

I have an interface and class defined like this:

interface Foo {
  constructor: typeof Foo;
}

class Foo {
  static bar = 'bar';

  constructor(data: Partial<Foo>) {
    Object.assign(this, data);
  }

  someMethod() {
    return this.constructor.bar;
  }

  prop1: string;
  prop2: number;
}

The interface is necessary so that this.constructor is strongly typed. However, it breaks my ability to pass a plain object into the class constructor:

const foo = new Foo({ prop1: 'asdf', prop2: 1234 });

// Argument of type '{ prop1: string; prop2: number; }' is not assignable to parameter of type 'Partial<Foo>'.
//  Types of property 'constructor' are incompatible.
//    Type 'Function' is not assignable to type 'typeof Foo'.
//      Type 'Function' provides no match for the signature 'new (data: Partial<Foo>): Foo'.

I understand the error message, but I don't know a way around it. Is there any way have a Partial<Foo> which allows me to pass a plain object? Here's a playground:

Playground

Ryan Wheale
  • 26,022
  • 8
  • 76
  • 96
  • Not sure what's your intention, seems like you trying to access static property from instance, that's not possible, also interface is redundant, [checkout this](http://www.typescriptlang.org/play/?strictPropertyInitialization=false&ssl=1&ssc=1&pln=16&pc=55#code/MYGwhgzhAEBiD29oG8BQ1oQC5iwS2GgCMwAnaAXmgHITTqBuVdaYeAO21IFdgt5SACgAmuMAC5oABTL4wIADwJ4APgCUKFhgDyRAFYBTPgDpIEPAHN2grAAs8EADTRRONUwwBfZhgjwAtgYAsgZ28MKCGmgYGKSh3KTscIjGdB7Q3iwADqTwWQCMklx47BbpOXkATEVYpCVlqJlsnFjQAGaIlNDsBgDuyfCCyNAVBZLUkMJt1M6j1TT5lQDMACzUGe5AA) – Medet Tleukabiluly Oct 25 '19 at 17:03
  • Thanks Eric. You can access static properties from an instance via `this.constructor`, and it is not uncommon to do so. The redundant interface is also necessary because you cannot type the `constructor` method on a class definition. This uses "interface merging", which is a feature of TypeScript and is also not uncommon: https://github.com/microsoft/TypeScript/issues/3841#issuecomment-502845949 – Ryan Wheale Oct 25 '19 at 20:07

3 Answers3

4

Here's the actual type creating a derived type from a class omitting the constructor (as in the question title) and keeping regular methods:

type NonConstructorKeys<T> = ({[P in keyof T]: T[P] extends new () => any ? never : P })[keyof T];
type NonConstructor<T> = Pick<T, NonConstructorKeys<T>>;

Usage with the Foo from the question:

type FooNonConstructorKeys = NonConstructorKeys<Foo>; // "prop1" | "prop2" | "someMethod"
type FooNonConstructor = NonConstructor<Foo>;
Terite
  • 1,097
  • 13
  • 23
1

I ended up finding what I needed in this wonderful answer:

how to remove properties via mapped type in TypeScript

The code in that answer creates a derived type containing only methods. I needed to do the inverse of that. The following NonMethods<T> helper creates a derived type with all of the methods removed.

type NonMethodKeys<T> = ({[P in keyof T]: T[P] extends Function ? never : P })[keyof T];  
type NonMethods<T> = Pick<T, NonMethodKeys<T>>; 

Here's the Playground

Ryan Wheale
  • 26,022
  • 8
  • 76
  • 96
0

Looks like you want to define an interface and then use it, then you have to define the properties in the interface itself and not the class.

interface Foo {
  prop1: string; // define your properties here
  prop2: number;
}

class Foo {
  static bar = 'bar';

  constructor(data: Partial<Foo>) {
    Object.assign(this, data);
  }

  someMethod() {
    return Foo.bar; // notice how I access static variables now
  }

}

const foo = new Foo({ prop1: 'asdf', prop2: 1234 });

Playground

weegee
  • 3,256
  • 2
  • 18
  • 32
  • 1
    Thanks. I need `this.constructor` because this is a base class (eg. abstract) which is extended by other classes. Each extended class will define its own "bar" value. – Ryan Wheale Oct 25 '19 at 21:45