A TypeScript Interface is like a more powerful type
- so to get a better understanding of interfaces we are going to start off by creating a type and then refactoring it to use an interface.
Table of contents
Introducing types
Let’s start by creating our custom type, which is going to be of an object nature. We’re going to say that the name
of the Pizza
will be of type string
and we’re also going to say that our pizzas are going to have some available sizes - such as 'small'
, 'medium'
or 'large'
. These sizes are all going to be a string array string[]
.
type Pizza = {
name: string;
size: string[];
}
Now we want to create a pizza
object and mark it upfront as a type of Pizza
. We can then later reassign the value.
let pizza: Pizza;
So what we’re going to create is a function called createPizza
that will accept a name: string
and some sizes: string[]
. Let’s assume that you’re using an API at this point where you’d go off to the backend, pass some JSON and then get back from the server a newly created pizza with a dynamic ID.
Free eBook
Directives, simple right? Wrong! On the outside they look simple, but even skilled Angular devs haven’t grasped every concept in this eBook.
- Observables and Async Pipe
- Identity Checking and Performance
- Web Components <ng-template> syntax
- <ng-container> and Observable Composition
- Advanced Rendering Patterns
- Setters and Getters for Styles and Class Bindings
What we’re going to do is use name
and sizes
, this is the shorthand syntax for creating object literals under the same name as the variables that we want to pass in. So name
is the same as name: name
.
function createPizza(name: string, sizes: string[]) {
return {
name,
sizes
}
}
Let’s assume that this is an asynchronous request and inside some other function we are then going to assign the result. So we’re going to say that we want to create the pizza, we need to pass in a name and the sizes that this pizza is available in. Here’s where we’re at so far:
type Pizza = {
name: string;
size: string[];
}
let pizza: Pizza;
function createPizza(name: string, sizes: string[]) {
return {
name,
sizes
}
}
pizza = createPizza('Pepperoni', ['small', 'medium']);
It might be that in a pizza restaurant they have different pizzas or different bases and they’re only available in certain sizes - but when we create a pizza we want to supply these defaults as the arguments. The main purpose here is to start thinking about our interfaces! Let’s now learn about TypeScript’s interface.
TypeScript Interfaces
If you were to hover over the pizza
variable you would see it’s of type pizza let pizza: Pizza
- but we’re not 100% sure that our createPizza
function returns us a pizza. This is known as an inferred type, where we do not describe the type but let TypeScript figure it out for itself.
What we could do now is add Pizza
as a type to our function and say that the pizza is going to be returned! Now it’s a guarantee that our function will return us what we’d like, providing the implementation was correct:
function createPizza(name: string, sizes: string[]): Pizza {
return {
name,
sizes
}
}
What we actually want to do is create an interface instead of a type as it is a preferred approach when we’re dealing with more complex data structures or data sets. To start, we will change our type Pizza
to interface Pizza
:
interface Pizza {
name: string;
size: string[];
}
There’s one interesting thing here, we do not need the equals =
operator to assign the type
a value, as interface
is a special TypeScript type and keyword.
An interface, much like a type, also creates that contractual agreement between perhaps a variable or a data structure. In our case we’re using this pizza
variable and we’re saying that at some point this Pizza
is then going to be of type pizza
.
let pizza: Pizza;
If we were to assign something else (for example if we try to add deal: true
) things will break:
function createPizza(name: string, sizes: string[]): Pizza {
return {
name,
sizes,
deal: true
}
}
…When we hover over this we will see an error - we’d expect this with a type as well because a type will also pick up on these errors.
With interfaces we can do some interesting things because with object-oriented programming we can do things such as extend particular classes and we don’t get this benefit with simply a type - but we do with an interface!
Let’s remove the deal: true
and see what we have.
interface Pizza {
name: string;
size: string[];
}
let pizza: Pizza;
function createPizza(name: string, sizes: string[]) {
return {
name,
sizes
}
}
pizza = createPizza('Pepperoni', ['small', 'medium']);
This is pretty much what we need to know for the basics of learning what an interface is. It’s a special type in TypesScript and it allows us to essentially define the structure or a shape of a particular object/data structure.
BONUS: Interfaces go nicely with classes, and there is a lot of overlap and confusion. We’ve put together a super resource on answering the question “Classes versus Interfaces”.
Combining Interfaces in TypeScript
Let’s create a Pizzas
interface which has a data
property which will be made up of a Pizza
array Pizza[]
. This is how you can combine different interfaces, and the same applies to using the type
keyword, however we see some additional benefits by using an interface.
interface Pizza {
name: string;
size: string[];
}
interface Pizzas {
data: Pizza[];
}
When we go to use an interface instead of a type, if you hover over interface Pizza
it says interface Pizza
, which is a token TypeScript will use if and when we write any code that may produce a compilation error. If we were to change this to a type
, assign it, and hover over it. We would see something different we would see type Pizza = {name: string; sizes: string[];}
so the type Pizza equals an object literal. Another small difference between types and interfaces.
The most important thing to remember is an interface is typically the preferred way that we can create a contract between some kind of variable and its data, and the shape of the data, that we are essentially telling TypeScript is what it looks like.