Home > Enterprise >  Class cannot implement TypeScript interface when using static methods
Class cannot implement TypeScript interface when using static methods

Time:05-25

TypeScript recognises static class methods as valid properties that adhere to an interface when passing the class object as an argument, however it does not like them when the same interface is used to implement the class.

E.g. In the example below, TypeScript accepts that the Dog class adheres to the AnimalMethods interface when it is passed as an argument inside the callAnimalMethods function, however upon attempting to constrain the Dog class structure by using implements AnimalMethods, the following error is given:

Class 'Dog' incorrectly implements interface 'AnimalMethods'.
  Property 'walk' is missing in type 'Dog' but required in type 'AnimalMethods'

Example:

// error here
class Dog implements AnimalMethods {
  public static walk(): string {
    return 'walk';
  }
}

interface AnimalMethods {
  readonly walk: () => string,
}

function callAnimalMethods(animalObj: AnimalMethods): void {
  animalObj.walk();
}

callAnimalMethods(Dog);

Could someone explain why TypeScript does not allow static methods like this to adhere to an interface, especially when the structure is recognised when using the class in an argument.

Playground link.

CodePudding user response:

An implements clause on a class declaration tells the compiler to check that instances of the class are assignable to the implemented type. You use implements to describe class instances, not the class constructor.

That is, something like

class Foo implements Bar { /* ... */ }

will compile without error if

const bar: Bar = new Foo();

compiles without error (modulo constructor arguments). In your example, the following are both errors:

const dog: AnimalMethods = new Dog(); // error
new Dog().walk() // error

So an instance of the Dog class is not a valid AnimalMethod, so class Dog implements AnimalMethods {} is and should be an error.


There is currently no analogous clause to say that the class constructor implements an interface. There's a longstanding open request at microsoft/TypeScript#33892 asking for something like this. If and when such a feature is ever implemented, it might look like:

// Not valid TS as of 4.6, don't write this:
class Dog implements static AnimalMethods {
    public static walk(): string {
        return 'walk';
    }
}

But for now it's not available to us. All we have are workarounds. And these workarounds are all similar to something like

callAnimalMethods(Dog);

That is, we can get the compiler to tell us whether or not the Dog value itself is assignable to AnimalMethods. You can't get this check right on the class Dog {} line, but you can get it close. For example:

type DogCheck = Implements<AnimalMethods, typeof Dog>; // okay
class Dog {
    public static walk(): string {
        return 'walk';
    }
}

where Implements is just a dummy helper type like

type Implements<T, U extends T> = void;

And you can see that it gives errors if the static side fails to satisfy the to-be-implemented interface:

type CatCheck = Implements<AnimalMethods, typeof Cat>; // error!
// -------------------------------------> ~~~~~~~~~~
//   Property 'walk' is missing in type 'typeof Cat'
class Cat {
    public static talk(): string {
        return 'meow';
    }
}

Playground link to code

  • Related