Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Avoiding fields declaration duplication in interface and class definition

I have an interface like

interface I {
  a : string;
  b : number;
  c : string;
}

And now I'm declaring a class which will have public members a, b, c as per the interface I.

So I declare

class C implements I {
  public a : string;
  public b : number;
  public c : string;
  // and then constructor, other fields, other methods
}

Is there a way for me to avoid duplication in declaring fields? I.e. I want to have those fields without actually declaring them, by the virtue of extending the interface. So all I'll be left to write something like :

class C implements I {
  // perhaps a magic statement that asserts that C should inherit all members from I
  // and then constructor, other fields, other methods
}

To add insult to injury, there's even more duplication in the constructor because I cannot just initialize the fields from an object literal satisfying interface I in a one liner

constructor(data : I) {
   this.a = data.a;
   this.b = data.b;
   this.c = data.c;
}

I do not have control over definition of the interface, I only have control over definition of the class (this means I cannot for instance turn interface into an abstract class...)

like image 790
Peeyush Kushwaha Avatar asked Jul 22 '26 02:07

Peeyush Kushwaha


1 Answers

YES, it's possible. It requires some non-intuitive code but it will properly make sure you don't add redundant code.

You need the following line outside the class definition:

interface I {
  a : string;
  b : number;
  c : string;
}

interface C extends I {}; //<--- this line 

class C implements I {
  constructor(data : I) {
    this.a = data.a;
    this.b = data.b;
    this.c = data.c;
  }
}

Playground Link

Now the TypeScript compiler will consider that the the class C has declared the same fields as the interface I.

It works like this:

  • interface C declares an interface called C.
  • extends I means it inherits the properties from the existing interface I.
  • {} is just the body of this interface C - it's empty, as we don't add or change anything.

On its own, it's not very impressive. However, there is already a class called C, so the interface with the same name refers to the same interface that the class has. In simple terms,

class A {
    public foo: string;
    constructor(data: string) {
        this.foo = data;
    }
}

compiles in JavaScript (target ES6+) to

class A {
    constructor(data) {
        this.foo = data;
    }
}

the information removed pertains to the interface of the class that TypeScript will use at compile time to make sure you use the class correctly. For example:

  • adding or referring to properties that aren't declared
  • using the declared properties (like foo: string) according to their type (e.g., not treating foo as a number).
  • calling the constructor with the correct number and type of arguments

Declaring an interface with the same name of a class will instead merge the declaration with that of the class. This is why the class interface can be manipulated. And in this case, we use extends (which is valid for interfaces) to enrich the class definition (where only implements is allowed).

As a reference I got the technique from this comment in an issue thread for the TypeScript repository on GitHub. The title of the GitHub issue is "Class declarations should implicitly implement interfaces" and the comment is by RyanCavanaugh from 17th of February 2016:

Note that in the latest version of TypeScript, you can use class/interface merging to do this:

interface Foo {
    a: number;
}

interface Baz extends Foo { }
class Baz {
   constructor() {
       console.log(this.a); // no error here
   }
}

hat tip @jeffreymorlan for reminding me of this

[Note: the latest released version at the time of the comment would have been 1.8 Beta]

like image 158
VLAZ Avatar answered Jul 24 '26 17:07

VLAZ



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!