Vince Varga
Vince Varga

Reputation: 6968

Constrain method signatures through interface using generics in TypeScript

I have two interfaces that extend a third one, for example

interface Animal { /* name, etc */ };
interface Dog extends Animal { /* dog specific things */ }
interface Cat extends Animal { /* cat specific things */ }

I want to create for the two extending classes "Model" classes, that simplify the work with the database

interface AnimalModel<A extends Animal> {
  all(): Promise<A[]>;
  get(id: string): Promise<A>;
  create(user: A): Promise<void>;
}

I went ahead and

class DogModel implements AnimalModel<Dog> {
  // Expected TypeScript to complain, but it does not, because Cat does extend the animal?
  async create(cat: Cat) { /* actual implementation, query the db */ }
}

What am I doing wrong? I expected class DogModel implements AnimalModel<Dog> methods to only accept and/or return Dogs, but it can be any Animal.

ps: I'm thinking about using an abstract class, so I might solve it differently, the question is still unanswered and I want to understand what is wrong with my code (the one in this question)

Upvotes: 1

Views: 36

Answers (2)

Vince Varga
Vince Varga

Reputation: 6968

TypeScript didn't complain, as the Dog and Cat interfaces only contained optional properties. When they have different required properties, TypeScript correctly raises an error

Upvotes: 1

Titian Cernicova-Dragomir
Titian Cernicova-Dragomir

Reputation: 250186

Typescript uses structural compatibility to determine type compatibility, so two types with the same structure are compatible. In your case if the two interface have compatible structure, you will not get an error. With the code you posted (with empty interfaces) the two interfaces are obviously compatible, but this example would also so trigger an error:

interface Dog extends Animal { eat(): void;  }
interface Cat extends Animal { eat(): void; scratch(): void; }

If the types contain incompatible fields then you will get an error, for example add a discrimination fields, or have fields/methods that are required in dog and are not present in cat. This types will trigger an error:

interface Dog extends Animal { eat(): void; bark(): void }
interface Cat extends Animal { eat(): void; scratch(): void; }

Upvotes: 1

Related Questions