user1620696
user1620696

Reputation: 11415

Interface and class in TypeScript

In C# there's a quite huge difference between interfaces and classes. Indeed, a class represents a reference-type, so that we can actually create objects modeled on that class, while interfaces are meant to be contracts that a class sign to in order to ensure the existence of a certain behavior. In particular we can't create instances of interfaces.

The whole point with interfaces is to expose behavior. A class implements it by giving one explicit implementation of said behavior.

In that case, although interfaces may contain properties, most of the time we care about interfaces because of behavioral issues. So most of the type, interfaces are just contracts of behavior.

On TypeScript, on the other hand, I've seem something that made me quite uneasy, and in truth I've seen this more than once, which is the reason for this question.

In one tutorial I saw this:

export interface User {
    name: string; // required with minimum 5 chracters
    address?: {
        street?: string; // required
        postcode?: string;
    }
}

But wait a minute. Why User is an interface? If we think like C#, User shouldn't be an interface. In truth, looking at it, it seems like we are defining the data type User, instead of a contract of behavior.

Thinking like we do in C#, the natural thing would be this:

export class User {
    public name: string;
    public address: Address;
}
export class Address {
    public street: string;
    public postcode: string;
}

But this thing of using interfaces like we do with classes, to just define a data type, rather than defining a contract of behavior, seems very common in TypeScript.

So what interfaces are meant for in TypeScript? Why do people use interfaces in TypeScript like we use clases in C#? How interfaces should be properly used in TypeScript: to establish contracts of behavior, or to define properties and object should have?

Upvotes: 31

Views: 13722

Answers (5)

alpinist
alpinist

Reputation: 4101

How interfaces should be properly used in TypeScript: to establish contracts of behavior, or to define properties and object should have?

Interfaces in TypeScript are shape contracts, describing the expected structure of an object. If a value has a certain interface annotation, you expect it to be an object featuring the members defined in the interface. Members can be values or functions (methods). Generally, their behavior (function bodies) is not part of the contract. But you can specify if they are readonly or not.

So what interfaces are meant for in TypeScript? Why do people use interfaces in TypeScript like we use clases in C#?

Typescript interfaces can play the same role as C# interfaces if they are expected to be implemented by TypeScript classes.

But not only a class can implement an interface; any kind of value can:

interface HelloPrinter {
    printHello(): void
}

The following object is not a class but nevertheless implements the interface:

{
    printHello: () => console.log("hello")
}

Thus we can do

const o: HelloPrinter = {
    printHello: () => console.log("hello")
}

and the TypeScript compiler won't complain.

The object implements our interface without forcing us to write a class.

Working with interfaces is more lightweight than working with (interfaces and) classes.

But if you need to know the type name (class/interface name) during runtime then classes are the right choice, because interface names are only known at compile time.

Upvotes: 3

Joe Hawkins
Joe Hawkins

Reputation: 9971

I also came to Typescript from a C# background and have wondered the same things. I was thinking along the lines of POCOs (is POTO a thing?)

So what interfaces are meant for in TypeScript?

The Typescript Handbook seems to say that interfaces are meant for "defining contracts within your code".

Why do people use interfaces in TypeScript like we use classes in C#?

I agree with @deceze's answer here.

John Papa expands on the subject of classes and interfaces on his blog. He suggests that classes are best suited for "creating multiple new instances, using inheritance, [and] singleton objects". So, based on the intent of Typescript interfaces as described in the Typescript Handbook and one man's opinion, it would appear that classes are not necessary to establish contracts in Typescript. Instead, you should use interfaces. (Your C# senses will still be offended.)

Interfaces should be properly used in TypeScript: to establish contracts of behavior, or to define properties and object should have?

If I understand the question, you are asking if interfaces should establish contracts of behavior or contracts of structure. To this, I would answer: both. Typescript interfaces can still be used the same way interfaces are used in C# or Java (i.e. to describe the behavior of a class), but they also offer the ability to describe the structure of data.

Furthermore, my coworker got on me for using classes instead of interfaces because interfaces produce no code in the compiler.

Example:

This Typescript:

class Car implements ICar {
    foo: string;
    bar(): void {
    }
}

interface ICar {
    foo: string;
    bar(): void;
}

produces this Javascript:

var Car = (function () {
    function Car() {
    }
    Car.prototype.bar = function () {
    };
    return Car;
}());

Try it out

Upvotes: 17

Matthias247
Matthias247

Reputation: 10426

Interfaces in typescript are similar to interfaces in C# in that they both provide a contract. However opposed to C# interfaces which only contain methods typescript interfaces can also describe fields or properties that objects contain. Therefore they can also be used for things which are not directly possible with C# interfaces.

A major difference between interfaces and classes in typescript is that interfaces don't have a runtime representation and there won't be any code emitted for them. Interfaces are very broadly usable. For example you can use object literals to construct objects with satisfy an interface. Like:

let user: User = {
  name: 'abc',
  address: {
    street: 'xyz',
  },
};

Or you can assign any data objects (e.g. received through JSON parsing) to an interface (but your pre-checks should assert that it's really valid data). Therefore interfaces are very flexible for data.

On the other hand classes have a type associated at runtime to them and there is code generated. You can check the type at runtime with instanceof and there's a prototype chain set up. If you define User as a class it won't be a valid user unless you call the constructor function. And you can't just define any kind of suitable data to be a User. You would need to create a new instance and copy the properties over.

My personal rule of thumb:

  • If I'm dealing with pure data (of varying sources) I use interfaces
  • If I'm modelling something which has an identity and state (and probably attached methods to modify the state) I'm using a class.

Upvotes: 11

Jason Kleban
Jason Kleban

Reputation: 20818

Using only the native deserialization mechanism, you cannot deserialize an instance of a specific class. You can only deserialize into a plain-old-javascript-object. Such objects can adhere to typescript interfaces but cannot be an instance of a class. If you need to deal with data that crosses a serialization boundary such as data expected from a webservice, use interfaces. If you need to generate new instances of such values yourself, just construct them literally or create a convenience function that returns them - objects that adhere to that interface.

A class can itself implement an interface, but it might get confusing if you expect to deal with both locally constructed class instances AND deserialized, reconstituted plain objects. You'd never be able to rely on the class-basis of the object and so there'd be no benefit of also defining it as a class for that exact purpose.

I've had success in creating a ServerProxy module responsible for sending code back and forth from a webservice - the webservice call and the returned result. If you're binding to knockout models or similar, you can have a class that encapsulates the ui-bound model with a constructor that knows how to lift a returned plain-old-javascript-object that adheres to the webservice's interface-only contract into an instance of your model class.

Upvotes: 1

deceze
deceze

Reputation: 522510

Consider that in Javascript, data is often exchanged as plain objects, often through JSON:

let data = JSON.parse(someString);

Let's say this data is an array of User objects, and we'll pass it to a function:

data.forEach(user => foo(user))

foo would be typed like this:

function foo(user: User) { ... }

But wait, at no point did we do new User! Should we? Should we have to write a class User and map all the data to it, even though the result would be exactly the same, an Object with properties? No, that would be madness just for the sake of satisfying the type system, but not change anything about the runtime. A simple interface which describes how the specific object is expected to look like (to "behave") is perfectly sufficient here.

Upvotes: 27

Related Questions