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

The "Open Interfaces" 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 re-declaring and combining interfaces, allowing for the augmentation of existing types, and the limitations of type aliases in this context. Questions from the audience regarding the implementation of interfaces and the impact of modifying interfaces on other files are also covered in this segment.


Transcript from the "Open Interfaces" Lesson

>> Interfaces are open. You may have heard this before if you've read a TypeScript book or something like it. So let's explore what that means. So we've got a function called feed, and we feed an animal. And in our case, AnimalLike just has this eat method on it, but let's say that we wanna do this.

This is a little morbid, sorry. Something like that. We have no isAlive method here. And let's say we were importing this alias from a library where we can't go and change it, necessarily, but we wanna sort of augment it. Well, the fact that interfaces are open means that we can have a second declaration here.

You know how if we were to create a variable x and then another variable x right below it, another declaration of the same name, JavaScript, ESLint would bark at you for that, TypeScript certainly will. You're not allowed to redeclare most things, but you can redeclare interfaces. And what ends up happening is all of the interface declarations kind of get smushed together.

And it's important to understand that when we do this, we're doing this everywhere the AnimalLike interface is used, meaning, because this is now in this file and TypeScript has read this. If we go back up here to our other AnimalLike stuff, we've got Dog2. Oops, I think we had isAlive in there as it was.

But just the way I want you to think about it is there's one place where the compiler is keeping track of what does it mean to be AnimalLike. And what we've done here is effectively, we have modified that everywhere AnimalLike is used. It's this one central thing, and everywhere we see AnimalLike, it now has isAlive.

This is useful because sometimes you get a type from a library or a type from the built-in things, the types that describe the JavaScript programming language, like that promise type that we were just looking at. And maybe you wanna add something to it. Well, you can do that with open interfaces.

You can kind of stack things on top of an existing interface. So let's look at that use case where we're augmenting existing types. So here we've got window, which is if you're writing code for a browser, like window.cookie here, window.document, right, it's this global object that's available to you.

And if we wanted to add a property to window, I wouldn't advise doing this too much like globals programming is fraught, but say you wanted to do something here, add an example property. Well, we could do this, and there's some syntax here we haven't explained yet, but I'll walk through it.

What we're saying here is we want to make a declaration of an interface in the global scope, right? This is not just in this one file, we want to add some types to the scope called global. Well, it's where setTimeout lives or the promise constructor, things that are available.

Any module you write, you always have these things at your fingertips, right? Right, setTimeout, setImmediate, they are in a global scope. In fact, they're on, They're on Window or on global if you're in Window. So now we're saying, all right, Window, the interface Window has a property on it called exampleProperty, which is a number, and look, that type error we had here before, it went away.

So if we comment this line out, it's like, exampleProperty does not exist. Well, we can make it exist. We can say, look, I'm gonna put something here. Let me put it there. Let me tell you what the type is. So we're using open interfaces to augment an existing type, which is pretty cool.

You cannot do this with a type alias. Interfaces are open, type aliases are not. If we were to go back to, where's the type alias here? Here. It's a duplicate identifier, we're not allowed. So it's a special feature of interfaces and it's very, very useful for cases like this.

All right, last thing we're gonna describe here, which we'll need for our exercise, which is coming up in a moment, recursive types. So let's say we have a data structure that we wanna represent here, and let's call it NestedNumbers, which can be a number or an array of nested numbers.

There's your recursion right there.
>> Is there a way to see the full implementation of an interface locally?
>> Yes.
>> Do you have to track down all the instances of the interface and keep a mental model?
>> Okay, good question, so Anthony asks, can we see the full implementation of an interface or do we have to track everything down?

The answer is sort of in between. It's not as bad as you'd think, promise. Yeah, maybe there's something weird with globals. All right, so I just Cmd clicked on promise, and look at what's open here on the right. I have a bunch of different files that describe interfaces.

And look at this, I've got 2015, there's something. There's es5, there's a promise. And there's something in 2018. It's almost like different releases of the JavaScript programming language might layer more and more things on top of promise. So if we look at 2018, there's the finally. Finally came with es2018, so, To kinda answer your question, yes, it's true that there are multiple declarations of an interface.

But VS Code in particular has done a pretty great job at letting you quickly find all of those declarations and sort of cycle through them and see if that's the one you want, or you go look at this one. This is the variable for promise, right? It's a promise constructor that kind of describes this promise, right?

It's the promise that I'm invoking, not the instance that comes back. It's a class, right, the constructor. So yeah, things are separated into different declarations, but they're all there if you Cmd click.
>> Since the interfaces are open, will it throw an error in other files, too?
>> Yeah, that's a very good question.

So Luis asks, since interfaces are open, will it throw an error in other files, too? It will, that is a corollary that falls out of what I was saying where there's one place where TypeScript keeps track of what these interfaces are. So in the case of promise, right, where we saw all of those declarations, every promise, including everything in your Node modules that deals with this concept of promise, they all have .finally on them.

They're all working with the same type. Type checking is a holistic operation that happens on your entire program. You could think of it, it's like happening at once at the same time. It's kind of hard to imagine, but we don't have a debugger that we can put in there.

It's just type checking happens, it's evaluated, and it either works or doesn't work. And there's one definition of each type in memory. It's like a source of truth. And when we use interfaces, we are kind of updating or adding to that single source of truth for each type.

So yeah, if you augment an interface in a way that is unfriendly to other code, it could cause problems. But it's additive, I don't think that we could do this. Yeah, we can't do this. There is already a declaration for a document. So we can't override document and make everybody think it's a number instead of the document object.

So you can't really stomp on things that already exist, but you can add to them. And so it doesn't really result in that much breakage in practice. But it could, if you were checking for the presence of this property on Window and you always relied on it not being there, and now it's there, a typeguard could flip in another direction.

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