7

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...)

5
  • Interface is just contract description. You have to define those properties when implementing interface. Well, this is the core idea of interfaces. And no, what you're describing it's not possible. With abstract class - you can, because properties will be defined there. Commented Jan 19, 2021 at 8:19
  • Well, if the argument for the downvote is that "the answer to your question is NO" then that's a poor use of downvote. Downvotes should be used to indicate quality of the question, "NO"s and "YES"s can be added as answers. Commented Jan 19, 2021 at 8:20
  • Apologies, I thought you were perhaps responding to my comment. Commented Jan 19, 2021 at 8:33
  • 1
    @sleepwalker the answer is definitely not "impossible". See my answer here. TS classes are actually an interface + implementation themselves, so you can use that system to explain to the compiler what the shape of your class is without repeating yourself. Commented Jan 19, 2021 at 8:33
  • 1
    @VLAZ yep, saw your comment. Makes sense. Good one answer. Commented Jan 19, 2021 at 8:35

3 Answers 3

11

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]

Sign up to request clarification or add additional context in comments.

5 Comments

This is amazing. Do you have links to docs where I could understand how "C" is both a class and an interface? And also what I {} means?
I {} is just part of the whole line. interface C declares an interface extends I means it inherits the same properties as I, and {} is the body of the interface - it's empty. I'll add this explanation and a link to the information in a short while - I had to dig out the link, it's from a bug thread on GitHub, so not very obvious place to look for it.
Awesome. Thanks. Also, magically Object.assign(this, data) works inside constructor now. Earlier, I was getting warnings that I haven't initialized the data members. I think there is a caveat - using this type hack disables all initialization warnings. But that's good enough for me :)
@PeeyushKushwaha I think this is that thread, which was mentioned by VLAZ: github.com/microsoft/TypeScript/issues/9699
@sleepwalker actually it's this one
1

I encountered a similar problem and tried to use declaration merging as used in VLAZ's answer. However the issue with that strictPropertyInitialization isn't enforced when using the merge method. Here is the playground with the answer and the initializations removed

1 Comment

While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - From Review
0

In a simple word => NO.

If your class implements the interface I, all attributs and methods declared in your interface must be declared/implemented in your class.

In OOP, interface can be considered as a contract. => If a class inplements an interface (a contract), this contract must be completly fulfilled

Comments

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.