TypeScript Tip of The Week — Using Classes & Interfaces
Hey guys, sorry I haven’t posted in a while I have been EXTRA busy the last few months! I am excited to be back with another TypeScript Tip. This week I want to talk about classes and interfaces. If you’re unfamiliar with interfaces they create a sort of structure for what an object or class is supposed to look like. They enforce that structure so that you will get an error if your class does not properly implement it.
When an interface enforces what an object structure is supposed to look like it also creates a contract with your code and outside code. To further explain this point, let’s have a look at an example.
// person-library.ts
export interface IPerson {
firstName: string;
lastName: string;
age: number;
}
export class Person implements IPerson {
firstName: string;
lastName: string;
age: number;
constructor(data: IPerson) {
this.firstName = data.firstName;
this.lastName = data.lastName;
this.age = data.age;
}
// Class Functions
}
If you’re wondering why the interface is named IPerson
instead of just Person
, the I
is usually used for interfaces as a general convention, but can also be used for interfaces that are implemented.
In this example, the Person
class is implementing the IPerson
interface. This means that the Person class must have all the properties of IPerson
. Let’s assume that this is part of a person
library that someone else is using. I mentioned that this can be used in external code to create a contract between the interface in the library and the external code.
// someone other code
import { IPerson, Person } from 'person-library';
interface IEmployee extends IPerson {
jobTitle: string;
salary: number;
}
class Employee extends Person implements IEmployee {
firstName: string;
lastName: string;
age: number;
jobTitle: string;
salary: number
constructor(data: IEmployee) {
super(data);
this.jobTitle = data.jobTitle;
this.salary = data.salary;
}
// Class Functions
}
const me = new Employee({firstName: 'Sam', lastName: 'Redmond', age: 100, jobTitle: 'Software Engineer', salary: 1000000});
This is a simple example showing exactly how the IPerson
interface can be extended with additional properties. This pattern provides a general structure and allows for code reusability. I mentioned that interfaces create contracts in your code. This means that when we use an interface from some external code it is guaranteed that it will have certain properties. You can use that guarantee to assume that certain properties will exist.
Let’s break down this code a little bit. When we extend the IEmployee
interface with the IPerson
interface we don’t have to define the properties in IPerson
. This is a useful feature of TypeScript so that you can declare interface properties once and then extend them later. We can do the same thing with the Employee
class. When we extend a class we also have to use the super
function in order to pass in the required information into the constructor of the extended class.
You will notice that we are passing in an IEmployee
into the super
function, but it should take an IPerson
. Since IEmployee
extends IPerson
it must have all the properties of IPerson
and can be used in its place in this case. This is an excellent example of creating contracts in your code using interfaces. Since all of the properties for IPerson
have been initialized by our use of the super
function we only need to initialize the properties unique to IEmployee
.
Following this pattern gives you the ability to easily re-use chunks of code. This keeps your code neat and succinct which will help you maintain it in the long run. I hope you enjoyed this TypeScript tip. Since the holidays are over I should be able to continue posting regularly. I will be posting a new TypeScript tip every Wednesday evening.
Thanks for reading!
If you’ve enjoyed this article I’d love for you to join my mailing list where I send out additional tips & tricks!
If you found this article helpful, interesting, or entertaining buy me a coffee to help me continue to put out content!