What is the difference between doing this
export class Comment {
likes: string;
comment: string;
constructor(likes: string, comment: string){
this.comment = comment;
this.likes = likes;
}
}
and this
export interface CommentInterface {
likes: string;
comment: string;
}
in relation to declaring an observable type
register: Observable<CommentInterface[]> {
return this.http.get()
}
As JB Nizet quite correctly points out, the deserialized JSON values that result from HTTP requests will never be instances of a class.
While the dual role (see below) of the
class
construct in TypeScript makes it possible to use aclass
to describe the shape of these response values, it is a poor practice because the response text will be deserialized into plain JavaScript objects.Note that throughout this answer I do not use a type for
Comment.likes
because in the question you have it as astring
, but it feels like anumber
to me, so I leave it up the reader.Class declarations in JavaScript and TypeScript:
In JavaScript, a class declaration
creates a value that can be instantiated using
new
to act as what is essentially a factory.In TypeScript, a class declaration creates two things.
The first is the exact same JavaScript class value described above. The second is a type that describes the structure of the instances created by writing
That second artifact, the type, can then be used as a type annotation such as in your example of
which says that
register
returns anObservable
of Arrays ofComment
class
instances.Now imagine your HTTP request returns the following JSON
However the method declaration
while it correctly allows callers to write
which is all well and good, it unfortunately also allows callers to write code like
Since comments are not actually instances of the
Comment
class the above check will always fail and hence there is a bug in the code. However, TypeScript will not catch the error because we said thatcomments
is an array of instances of theComment
class and that would make the check valid (recall that theresponse.json()
returnsany
which can be converted to any type without warnings so everything appears fine at compile time).If, however we had declared comment as an
interface
then
getComments
will continue to type check, because it is in fact correct code, butgetTopComment
will raise an error at compile time in the if statement because, as noted by many others, aninterface
, being a compile time only construct, can not be used as if it were a constructor to perform aninstanceof
check. The compiler will tell us we have an error.Remarks:
In addition to all the other reasons given, in my opinion, when you have something that represents plain old data in JavaScript/TypeScript, using a class is usually overkill. It creates a function with a prototype and has a bunch of other aspects that we do not likely need or care about.
It also throws away benefits that you get by default if you use objects. These benefits include syntactic sugar for creating and copying objects and TypeScript's inference of the types of these objects.
Consider
If
Comment
is an interface and I want to use the above service to create a comment, I can do it as followsIf
Comment
were a class, I would need to introduce some boiler plate by necessitating theimport
ofComment
. Naturally, this also increases coupling.That is two extra lines, and one involves depending on another module just to create an object.
Now if
Comment
is an interface, but I want a sophisticated class that does validation and whatnot before I give it to my service, I can still have that as well.In short there are many reasons to use an
interface
to describe the type of the responses and also the requests that interact with an HTTP service when using TypeScript.Note: you can also use a
type
declaration, which is equally safe and robust but it is less idiomatic and the tooling aroundinterface
often makes it preferable for this scenario.As in most other OOP languages: For classes you can create instances (via their constructor), while you cannot create instances of interfaces.
In other words: If you just return deserialized JSON, then it makes sense to use the interface to avoid confusion. Lets assume you add some method
foo
to yourComment
class. If yourregister
method is declared to return aComment
then you might assume that you can callfoo
on the return value of register. But this wont work since whatregister
effectively returns is just deserialzed JSON without your implementation offoo
on theComment
class. More specifically, it is NOT an instance of yourComment
class. Of course, you could also accidentally declare thefoo
method in yourCommentInterface
and it still wouldn't work, but then there would be no actual code for thefoo
method that is just not being executed, making it easier to reason about the root cause of your call tofoo
not working.Additionally think about it on a semantic level: Declaring to return an interface gurantees that everything that is declared on the interface is present on the returned value. Declaring to return a class instance gurantees that you... well... return a class instance, which is not what you are doing, since you are returning deserialized Json.