11

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?

4
  • What is the point constructor: any ? All objects have this property already ..\ Commented Mar 4, 2019 at 15:29
  • Is there a way to tell typescript that the class implements the interface, without explicitly declaring every interface property? No. That's what classes are for, not interfaces. Interfaces DO NOT exist at runtime. Make a class instead of an interface and extend it instead of implementing it. Commented Mar 4, 2019 at 15:31
  • why is Dog a class at all? If you create everything during the build, let it be interface as well Commented Mar 4, 2019 at 15:32
  • @smnbbrv I don't create everything during the build, just tack on the missing parts from the interface. My example is not identical to our real-world scenario, but Dog has its own properties, and we actually need to run new Dog(), requiring a concrete instance of the class. Commented Mar 4, 2019 at 17:00

2 Answers 2

17

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"; 
Sign up to request clarification or add additional context in comments.

2 Comments

Your autoImplment solution does exactly what I was looking for! Thanks!
I'd advise autoImplement() be defined so that it only allows T to be a weak type, at least with --strictNullChecks on.
0

I believe that this design is flawed. Just because an Animal doesn't have a name it still should count as an Animal. By implementing OptionalAnimal you gained nothing, because you have to check for every interface field if it is really present in all classes that implement OptionalAnimal.

To have optional fields in an interface use a typed union with undefined. Now you still have implementation guaranties from the interface and only have to check the optional fields.

For better type narrowing I've added a type-Field to the interface:

interface Animal {
  type: string;
  name: string | undefined;
}

interface HasConstructor {
  constructor: any;
}

class Dog implements Animal, HasConstructor {
  public constructor() {}
  type = "Dog";
  public breed: string;
  public name: string = undefined;
}

// usage

var animals = new Array<Animal>();

var spot = new Dog();
spot.name = "Spot";
animals.push(spot);

animals.push(new Dog());
animals.push(new Dog());
animals.push(new Dog());

var pluto = new Dog();
pluto.name = "Pluto";
animals.push(pluto);

for (let animal of animals) {
  console.log("Animal:", animal.type, animal.name);
}

1 Comment

Hmm, it's possible you misunderstood the original question? In the first paragraph, it states "The interface properties are appended during the build process and WILL exist at runtime." These aren't optional fields, they are guaranteed fields that will 100% exist when the code is executed. So there's no need to "check for every interface field if it is really present".

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.