TypeScript 5+ Fundamentals, v4

Interface extends & implements

TypeScript 5+ Fundamentals, v4

Check out a free preview of the full TypeScript 5+ Fundamentals, v4 course

The "Interface extends & implements" Lesson is part of the full, TypeScript 5+ Fundamentals, v4 course featured in this preview video. Here's what you'd learn in this lesson:

Mike discusses how interfaces can be used to give a type a name and how they can be used for inheritance. The extends and implements keywords, as well as the differences between interfaces and type aliases are also covered in this segment.


Transcript from the "Interface extends & implements" Lesson

>> All right, let's talk a little bit about interfaces. This is the other option here for giving your type a name. And we can do something very similar here, in the sense that we can make our printAmount function, and if we look at amt, we've got the currency and the value in there, so we are giving a type a name.

Note that there's no equals sign here, this would be invalid syntax if we put an equals sign here. We would say this is an interface declaration. Interfaces cannot model every possible type that TypeScript can create. So for example, I can't do this, I cannot. I can't create a union type in an interface, at least not on this outer object.

I could do like this, but we've got to make sure that this thing is object-ish. And part of that has to do with one of the key reasons that you would reach for an interface. So, that is for inheritance. So the first thing we're going to talk about is the extends keyword, right?

So this is one of the two heritage clauses that TypeScript allows. Extends is one, implements is the other, but they both describe inheritance. So first I wanna remind you of the regular JavaScript extends behavior, right? We've got a base class AnimalThatEats, it takes in a food. Let's say that's a string representing the type of food, just make those type errors go away.

And then we can create a Cat that extends AnimalThatEats, and we get a new method on there where it meows. So if we create the Cat, it can eat. I mean, we haven't invoked. I guess, cats eat cat food. And then we can make it meow. So this is just JavaScript classes at work here.

Here's how it works with interfaces. All right, we're gonna define an interface called Animal. Now note, interfaces don't have a body, I couldn't implement a method here, they're just types. They kind of look like classes a little bit, but it's like a class without the implementation. You can't instantiate an interface.

You're just kind of describing fields and methods, things of that nature. So we can create one that's Animal, we can create another interface that sort of extends Animal. So mammals, they are alive, they have fur or hair color. I don't know if all mammals have fur or hair, but.

And then a Hamster is a mammal, and a hamster squeaks, and so we can extend it one more time, and look at what we can do. We've got careForHamster, and I've got all of that functionality on here. So that's extends. And the mental model here is, in classes, you can have a class that extends a class.

And you can have an interface that extends an interface. So extends is for like things. We're gonna talk about implements in a second, which allows a class to implement an interface. But extends is the one you reach for when it's like things, class for class, interface for interface.

All right, let's talk about implements. So, here we've got an interface AnimalLike, we'll make this string again, and we've got Dog implements AnimalLike, and we've got the Dog barks, what's the error? Dog incorrectly implements interface, AnimalLike. Property eat is missing in type Dog but required in type AnimalLike.

So what we're seeing here is that an interface represents a contract that Dog must adhere to. If you implement AnimalLike, you gotta flesh out eat, we gotta create a method in there. So we could explicitly create it or I'm sure VS Code, yep, there you go, implement interface and all like.

So it'll create this for us, right? Anything that you're missing, VS Code will populate that for you. And it's gonna start with "Method- not- implemented", but I think we had a function, consumeFood will do that. And so now Dog is happy. Implements is about using an interface to define a contract that a class must adhere to.

Now I'm gonna loop back to when I said, interfaces can't be used to describe any old type that you can create in TypeScript. Well, what would it mean to have something like this? Something like that, it starts to get really confusing as to what this sorta even mean.

Dog implements null, can we do that? What does it mean for an instance of this class to be a null? It doesn't make any sense. There are certain minimum requirements we have for like, what does an instance of a class look like? And that requires that it is an object type, right?

So there are some things that an interface can't do. Let's put all of this together. So we've got a living organism. It's just another version of our AnimalLike. We've got CanBark, And here we go. But we can have Dog2, it's another class, just so I don't collide with my first one.

But we can extend LivingOrganism, which is a base class. Remember extends, classes extend other classes. And then we can have it implement two interfaces if we like. And we've got bark, we've got eat, make that a string again. So if we break this down, so we extend LivingOrganism, so we get isAlive, we get that for free.

isAlive is not in this class specifically, it's in the base class, we get it cuz it's in the base class. AnimalLike is an interface that requires an isAlive method. There it is, no, sorry, it's eat. Yeah, it's eat, so we get isAlive. Did we have another interface up here that was isAlive?

Animal, we could add that one too, Right? Yeah, we make Animal happy because this base class, I mean, we already have the method that Animal wants, right? AnimalLike requires us to have, what was it? Eat, and we have our eat. CanBark requires the bark, and so it's there.

So these all represent kind of contracts that this class and its base classes must live up to, right? Whatever the reified class is, when you flatten out the class, what methods are on this, what properties are on this, regardless of where they came from, a base class or this class.

And then this is where we're actually building on top of a fully-defined class that has methods and has class fields and has those kinds of things. But you can use them all together, these are both kinds of heritage clauses, just meaning it has to do with inheritance that are supported in TypeScript.

And Michael asks, could we use extends instead of implements for the Dog2? Michael, what would you have me extend here or implement? This here is a list of interfaces, and this is a class. So if we put an interface up here, Well, first you can't because TypeScript doesn't allow true multiple inheritance, which would be like I have multiple base things.

So even if it allowed that, AnimalLike is not a class, and so we couldn't put it up there. So at most you can have one extends, but you can have as many implements as you like, and they must all be interfaces. So you couldn't do, I don't think you could do this.

I can think of a way it could possibly work. Yeah, it is working here. What's happening here is, classes are two things. There's a class which has a value, the function that you call new on, and then there's an interface, which is what is the shape of an instance of the class?

It's actually both things sandwiched on top of each other. So when you're using living organism in this position here, you're using the interface side of the class. If you want to learn more about this, take the intermediate TypeScript course, where we will discuss declaration merging. And that's where we can learn how are these things sandwiched on top of each other.

Just know that what we're seeing here, though, is we incorrectly implement the interface here. isAlive is missing, why is it missing? All that LivingOrganism is giving us, it's basically giving us just the isAlive function that returns a Boolean. It doesn't give us the implementation function, it kinda just gives us this.

So, this isAlive thing, it's not giving us functionality from LivingOrganism. It's imposing a new requirement on us to implement our own isAlive function, which returns a Boolean, it would be equivalent to this. That's effectively what's happening here, it's now being used. The type of the class is being used as an interface as opposed to using it as a base class, which gives us real object-oriented inheritance with like, the method is there, if you trace prototypes back, you would see it.

A nuanced question, I thought it might or might not work cuz I could think of good reasons for both, but yeah.
>> So would you wanna make an interface for Dog that just combines those three, and then, rather than implements a list of three-
>> You could.
>> Is there a point to doing that, or was it just-

>> Sometimes.
>> Okay.
>> So we would do this. Something like that. But, because it's like, right, interfaces don't implement interfaces. Interfaces have no implementation. So we'd use extenss, now we've got DogLike, and we could do this. Now, why might we do this? Well, it's conceivable that there could be other things that are Dog-shaped and we want people to be able to use those in ways that don't require it to be specifically a dog.

I'll show you an example, and we're gonna look at some really narrowly TypeScript code. There's our interface for Promise. I've got my dot then, I've got my catch, we also have PromiseLike, which is just dot then. So sometimes you wanna say, look, all I need for Promise chaining is then, I can actually await something with a dot then.

I don't need the dot catch to be there. I may not need the dot finally to be there. So this is an example of teasing out the very essence of something, even though any given real promise that you're passing around, be it a native promise that's part of what the JavaScript language now provides, or there used to be libraries you'd bring in for promises.

They could all conform to this, and it didn't matter if you were using like some libraries promise or a native promise or whatever, as long as they're all promise-like, you can sort of interchange them for each other, right? That's when you would wanna tease out an interface like this, that would be the benefit, right?

Let's say you're like, look, I've got a bunch of different libraries that do dog things, they have different ways of modeling them maybe, but they all have to conform to this. And maybe this is my dog, the way I think about dog. And as long as it implements DogLike, I can have a bunch of functions out there that just require a dog-like thing.

And they'll work with anybody who conforms to that interface. That's one of the powers of structural typing, right? It doesn't have to come from this constructor, it can be anything that describes the right shape. And this is an example of sort of flattening a bunch of interfaces together.

And you could expose that outside of this library, nobody needs to worry about this. They just know it's a DogLike thing. And these could be a bunch of very granular little things that you're sorta stacking up one by one. But they're just gonna grab DogLike, does that make sense?

>> Yeah.
>> Mohd, M-O-H-D, you're asking, can we do class extends class extends class? That's a good question. Technically, yes, I think. Not allowed in the same, yeah, see. You can create anonymous classes. You could do that, [LAUGH], but this is some weird code here that technically works, cuz this is just an expression here that gives you a class.

And then this is another expression that gives you a class, effectively, but, I wouldn't. Many things are possible, let's talk about advisable things [LAUGH]. All right, okay moving on. Let's talk a little bit about type aliases, again, and how they work with classes. So we'll have a type alias called CanJump and we'll say we can jump to a height that is a number, let's say in inches or meters or whatever it is.

And we've got another Dog class, Dog3, it's gonna eat food, that's a string, and jump to height, it's always gonna return 1.7. That's great, and if we were to introduce this, I think everything's still okay. And this is because we're introducing a union type that, well, first off, we're conforming to this type.

jumpToHeight must return either a number or a tuple of two numbers. We're being a little more specific here. We only return a number from this class, but it's enough to conform to this interface. We're not obligated to handle all possibilities. All that matters is the return type of this function, is the set of all numbers allowed in the set represented by this, and yes, it is.

It's a specific set, doesn't include any of the tuples, but it's in there, all numbers are in that set. All right, so let's replace that type, CanBark. Sorry, CanJump, We'll add this, this is a new type alias. I'm gonna call it CanBark2, and let's try this. So, what we've got here, CanBark2 is a number, think of it that way, although adding that first pipe is not harmful.

It's a number or this object, and we're having a problem here. A class can only implement an object type or intersection of object types with statically known members. So number is not an object type, we can't have a class that extends or implements number. This is why I wouldn't advise using type aliases this way, you can use them.

You can get lucky or be careful, however you wanna think about it. CanJump is not causing us any problems. We could just as easily create an interface called CanJump with the same method in it. It's an object type either way. TypeScript actually doesn't care where these come from, whether they're interfaces or type aliases.

But when it resolves them, they must be the kind of thing that can model an instance of a class. And having the possibility that the instance of a class is a number, that's off, that's weird. We won't allow that. So this is why, if you intend to use it, type this way, I would advise making it an interface because it'll appropriately bust you if you start doing things that are just incompatible with the implements keyword.

Interfaces will always be compatible with the implements keyword. The rules you have for what you can use interfaces to describe, inherently, it is like exactly what can be on an instance of a class. LJ says, basically don't use type for what you would use interfaces for. Yeah, I think that's another way of saying what I'm saying here, if you're gonna do this, use an interface.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now