Home > other >  Assigning type interface to an instance of a class implementing interface with added properties
Assigning type interface to an instance of a class implementing interface with added properties

Time:08-10

I'm not sure if my question is clear, so I'll try to elaborate. Say I have a class MyClass that implements an interface MyInterface. Apart from the properties required by the implementation (in this case, myProp1), it has an additional property myProp2 as shown in the snippet below:

interface MyInterface {
  myProp1: string;
}

class MyClass implements MyInterface {
  myProp2 = '2';
  constructor(public myProp1: string) {}
}

Then, upon creating an instance of the class, I assigned it the MyInterface type. TypeScript doesn't complain even though instance has properties not specified in MyInterface:

let instance: MyInterface = new MyClass('1'); // no type error

But when attempting to access the added property (myProp2), I get an error:

console.log(instance.myProp2); // 'myProp2' does not exist on type 'MyInterface'. Did you mean 'myProp1'?

Is this a bug, or is it the intended behavior here?

CodePudding user response:

This is working as intended. TypeScript has structural subtyping, which means that a value of type X is assignable to a variable of type Y if every member required in type Y is present in type X.

So all that's needed for a value to be a valid MyInterface is for it to have a myProp1 property of type string. And every instance of MyClass does have such a property (which is why implements MyInterface compiles without error), so according to TypeScript, it's perfectly fine to assign a MyClass instance to a variable of type MyInterface.

It is true that TypeScript sometimes does complain about extra properties, via excess property checking, but this only triggers in certain circumstances; in particular if you assign an object literal with more properties than expected. Since the expressionnew MyClass('1') is not an object literal, there is no warning. This is described as being intentional in microsoft/TypeScript#5303.


Furthermore, the compiler does not keep track of what's been assigned to a variable of a non-union type. The type of instance is the non-union MyInterface type, and so the assignment instance = new MyClass('1') does not change that. It is not narrowed to MyClass, and thus you cannot access the myProp2 property through instance (as MyInterface has no known myProp2 property).

Note that this differs from the behavior on union-typed variables, where assignments will narrow the variable to just those union members with which the assigned value is compatible:

let instance: MyClass | Date = new MyClass('1');
instance.myProp2; // okay

This means that you really don't want to annotate a variable's type with a non-union type unless you want that variable to take on different values of that type and you don't need to keep track of which value it has. If instance will always be an instance of MyClass and that's all you need to know, then you can annotate it as such:

let instance: MyClass = new MyClass('1');
instance.myProp2; // okay

or don't annotate it at all and let the compiler infer its type to be MyClass:

let instance = new MyClass('1');
instance.myProp2; // okay

Generally speaking it's a good idea to let the compiler infer types for you and only annotate things if you need to exert more control than inference gives you.

Playground link to code

CodePudding user response:

It is indeed the intended behaviour.

  • Related