Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript class extending Partial interface

Tags:

typescript

I would like to create a class that has all the properties of an interface, but does not actually declare those properties itself.The interface properties are appended during the build process and WILL exist at runtime.

I found this post that pointed me in the direction of using Partial<T>...but that doesn't seem to work. The following code produces no compile errors.

interface Animal {
    name: string;
}

interface HasConstructor {
    constructor: any;
}
//Do this to supress this error: "Type 'Dog' has no properties in common with type 'Partial<Animal>"
type OptionalAnimal = Partial<Animal> & HasConstructor;

class Dog implements OptionalAnimal {
    public constructor() {

    }
    public breed: string;
}

However, the name property is not available on the instance of Dog.

var spot = new Dog();
spot.name = "Spot"; //ERROR: Property 'name' does not exist on type 'Dog'

I can get around this issue by creating another type and referencing it like this:

type AnimalDog = Dog & Animal;

var spot: Animal = new Dog() as any;
spot.name = "Spot";

However, I can't construct a new instance of AnimalDog, and have to cast as any to get the types to line up, so I'm left using both AnimalDog and Dog in my code depending on the scenario. This also produces compile errors inside of Dog when referencing Animal types.

Is there a way to tell typescript that the class implements the interface, without explicitly declaring every interface property?

like image 908
TwitchBronBron Avatar asked Mar 04 '19 15:03

TwitchBronBron


1 Answers

The problem is that Partial<T> will only allow you to implement the members will not require you to do so, and if you don't implement the member it will not be in the class.

You can create a function that returns a class and this class will implement the interface. The returned class will not actually have to declare any of the fields so they will all be undefined but this should be fine since the fields have to be optional anyway.

interface Animal {
    name: string;
}

type OptionalAnimal = Partial<Animal>;
function autoImplement<T>(): new () => T {
    return class { } as any;
}
class Dog extends autoImplement<OptionalAnimal>() {
    public constructor() {
        super();
    }
    public breed: string;
}

var spot = new Dog();

spot.name = "Spot"; // ok now

You can also cast the Dog class to specify that the returned instance has the members of Animal but these new members will not be accessible from inside the class:

interface Animal {
    name: string;
}

class _Dog {
    public constructor() {

    }
    public breed: string;
}

const Dog = _Dog as { new(): _Dog & Partial<Animal> } & typeof _Dog
type Dog = InstanceType<typeof Dog>

var spot = new Dog();

spot.name = "Spot"; 
like image 72
Titian Cernicova-Dragomir Avatar answered Oct 31 '22 10:10

Titian Cernicova-Dragomir