This content originally appeared on Envato Tuts+ Tutorials and was authored by Monty Shokeen
We began this series with an introductory tutorial that introduced you to different TypeScript features. It also taught you how to install TypeScript and suggested some IDEs that you can use to write and compile your own TypeScript code.
In the second tutorial, we covered different data types available in TypeScript and how using them can help you avoid a lot of errors. Assigning a data type like a string
to a particular variable tells TypeScript that you only want to assign a string to it. Relying on this information, TypeScript can point it out to you later when you try to perform an operation that is not supposed to be done on strings.
In this tutorial, you will learn about interfaces in TypeScript. With interfaces, you can go one step further and define the structure or type of more complex objects in your code. Just like simple variable types, these objects will also have to follow a set of rules created by you. This can help you write code more confidently, with less chance of error.
Creating Our First Interface
Let's say you have a lake object in your code, and you use it to store information about some of the largest lakes by area around the world. This lake object will have properties like the name of the lake, its area, length, depth, and the countries in which that lake exists.
The names of the lakes will be stored as a string. The lengths of these lakes will be in kilometers, and the areas will be in square kilometers, but both of these properties will be stored as numbers. The depths of the lakes will be in meters, and this could also be a float.
Since all these lakes are very large, their shorelines are generally not limited to a single country. We will be using an array of strings to store the names of all the countries on the shoreline of a particular lake. A Boolean can be used to specify if the lake is salt water or fresh water. The following code snippet creates an interface for our lake object.
interface Lakes { name: string, area: number, length: number, depth: number, isFreshwater: boolean, countries: string[] }
The Lakes
interface contains the type of each property that we are going to use while creating our lake objects. If you now try to assign different types of values to any of these properties, you will get an error. Here is an example that stores information about our first lake.
let firstLake: Lakes = { name: 'Caspian Sea', length: 1199, depth: 1025, area: 371000, isFreshwater: false, countries: ['Kazakhstan', 'Russia', 'Turkmenistan', 'Azerbaijan', 'Iran'] }
As you can see, the order in which you assign a value to these properties does not matter. However, you cannot omit a value. You will have to assign a value to every property in order to avoid errors while compiling the code.
This way, TypeScript makes sure that you did not miss any of the required values by mistake. Here is an example where we forgot to assign the value of the depth
property for a lake.
let secondLake: Lakes = { name: 'Superior', length: 616, area: 82100, isFreshwater: true, countries: ['Canada', 'United States'] }
The screenshot below shows the error message in Visual Studio Code after we forgot to specify the depth
. As you can see, the error clearly points out that we are missing the depth
property for our lake object.
Making Interface Properties Optional
Sometimes, you might need a property only for some specific objects. For example, let's say you want to add a property to specify the months in which a lake is frozen. If you add the property directly to the interface, as we have done until now, you will get an error for other lakes that do not freeze and therefore have no frozen
property. Similarly, if you add that property to lakes that are frozen but not in the interface declaration, you will still get an error.
In such cases, you can add a question mark (?
) after the name of a property to set it as optional in the interface declaration. This way, you will not get an error for either missing properties or unknown properties. The following example should make it clear.
interface Lakes { name: string, area: number, length: number, depth: number, isFreshwater: boolean, countries: string[], frozen?: string[] } let secondLake: Lakes = { name: 'Superior', depth: 406.3, length: 616, area: 82100, isFreshwater: true, countries: ['Canada', 'United States'] } let thirdLake: Lakes = { name: 'Baikal', depth: 1637, length: 636, area: 31500, isFreshwater: true, countries: ['Russia'], frozen: ['January', 'February', 'March', 'April', 'May'] }
Using Index Signatures
Optional properties are useful when quite a few of your objects are going to use them. However, what if each lake also had its own unique set of properties like economic activities, the population of different kinds of flora and fauna flourishing in that lake, or the settlements around the lake? Adding so many different properties inside the declaration of the interface itself and making them optional is not ideal.
As a solution, TypeScript allows you to add extra properties to specific objects with the help of index signatures. Adding an index signature to the interface declaration allows you to specify any number of properties for different objects that you are creating. You need to make the following changes to the interface.
In this example, I have used an index signature to add information about different settlements around the lakes. Since each lake will have its own settlements, using optional properties would not have been a good idea.
interface Lakes { name: string, area: number, length: number, depth: number, isFreshwater: boolean, countries: string[], frozen?: string[], [extraProp: string]: any } let fourthLake: Lakes = { name: 'Tanganyika', depth: 1470, length: 676, area: 32600, isFreshwater: true, countries: ['Burundi', 'Tanzania', 'Zambia', 'Congo'], kigoma:'Tanzania', kalemie: 'Congo', bujumbura: 'Burundi' }
As another example, let's say you are creating a game with different kinds of enemies. All these enemies will have some common properties like their size and health. These properties can be included in the interface declaration directly. If each category of these enemies has a unique set of weapons, those weapons can be included with the help of an index signature.
Read-Only Properties
When working with different objects, you may need to work with properties that should only be modified when we first create the object. You can mark these properties as readonly
in the interface declaration. This is similar to using the const
keyword, but const
is supposed to be used with variables, while readonly
is meant for properties.
TypeScript also allows you to make arrays read-only by using ReadonlyArray<T>
. Creating a read-only array will result in the removal of all the mutating methods from it. This is done to make sure that you cannot change the value of individual elements later. Here is an example of using read-only properties in interface declarations.
interface Enemy { readonly size: number, health: number, range: number, readonly damage: number } let tank: Enemy = { size: 50, health: 100, range: 60, damage: 12 } // This is Okay tank.health = 95; // Error because 'damage' is read-only. tank.damage = 10;
Functions and Interfaces
You can also use interfaces to describe a function type. This requires you to give the function a call signature with its parameter list and return type. You also need to provide both a name and a type for each of the parameters. Here is an example:
interface EnemyHit { (name: Enemy, damageDone: number): number; } let tankHit: EnemyHit = function(tankName: Enemy, damageDone: number) { tankName.health -= damageDone; return tankName.health; }
In the above code, we have declared a function interface and used it to define a function that subtracts the damage dealt to a tank from its health. As you can see, you don't have to use the same name for parameters in the interface declaration and the definition for the code to work.
Extending and Combining Interfaces
Interfaces are basically used to describe the type of objects that you want to create in TypeScript using different properties. We can use them to set the structure required to implement other functionality. When working on projects, you will come across situations where two or more objects will share a variety of properties. Defining a new interface for each of those variants will result in a lot of code duplication.
A better alternative here is to extend our previous interface to accommodate more specific properties. Here is an example:
interface Point { x: number, y: number } interface Rectangle extends Point { width: number, length: number } interface Cuboid extends Rectangle { height: number } function volumeCuboid(cuboid: Cuboid) { let volume = cuboid.length*cuboid.width*cuboid.height; console.log(`Volume: ${volume}`); } let cuboid: Cuboid = {x: -22, y: 28, width: 12, length: 32, height: 20} volumeCuboid(cuboid);
In the above code, we defined three different interfaces to describe points, rectangles and cuboids. A point only has two properties that determine its x
and y
coordinates. We extend the Point
interface with a Rectangle
and add two more properties called width
and length
. Finally, the Cuboid
interface extends Rectangle
and adds a height
property. It automatically gets all other properties so it has five different properties.
We also created a variable called cuboid
and told TypeScript that it is of type Cuboid
. This meant that we were required to specify a value for all the five properties. The function volumeCuboid
also accepts an object of type Cuboid
to calculate its volume.
One more thing that I would like to clarify here is that iInterfaces are present only in TypeScript and not JavaScript. As a result, compiling the above code will generate the following in JavaScript.
function volumeCuboid(cuboid) { let volume = cuboid.length * cuboid.width * cuboid.height; console.log(`Volume: ${volume}`); } let cuboid = { x: -22, y: 28, width: 12, length: 32, height: 20 }; volumeCuboid(cuboid); // Volume: 7680
TypeScript also has a concept of intersection types where we can use the &
operator to combine two interfaces. The resulting type will have members of both the interfaces. Here is an example:
interface Circle { radius: number } interface Rectangle { width: number, length: number } type RoundedRectangle = Circle & Rectangle; function areaRoundedRectangle(rR: RoundedRectangle) { let area_lw = (rR.length - 2*rR.radius)*(rR.width - 2*rR.radius); let area_lr = 2*rR.length*rR.radius; let area_wr = 2*rR.width*rR.radius; let area_r = 22*Math.pow(rR.radius, 2)/7; return area_lw + area_lr + area_wr + area_r; } let myRoundRect: RoundedRectangle = {radius: 7, width: 30, length: 50}; console.log(areaRoundedRectangle(myRoundRect));
In the above code, we created a new type RoundedRectangle
using two predefined types. This way the RoundedRectangle
type expects three properties.
We could have also created a new RoundRectangle
interface in the above example and our function would still have calculated the area properly. However, there are some differences which would affect your decision of going with intersection or extension of interfaces. For example, multiple interface declarations with the same name are acceptable and will be merged. On the other hand, type redeclarations will result in error.
Final Thoughts
This tutorial introduced you to interfaces and how you can use them to make sure you are writing more robust code. You should now be able to create your own interfaces with optional and read-only properties.
You also learned how to use index signatures to add a variety of other properties to an object that are not included in the interface declaration. This tutorial was meant to get you started with interfaces in TypeScript, and you can read more on this topic in the official documentation.
In the next tutorial, you will learn about classes in TypeScript.
This content originally appeared on Envato Tuts+ Tutorials and was authored by Monty Shokeen
Monty Shokeen | Sciencx (2017-08-21T21:01:20+00:00) TypeScript for Beginners, Part 3: Interfaces. Retrieved from https://www.scien.cx/2017/08/21/typescript-for-beginners-part-3-interfaces/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.