DEV Community

Cover image for TypeScript Interfaces: Crafting Code with Creative Precision
bilel salem
bilel salem

Posted on

TypeScript Interfaces: Crafting Code with Creative Precision

Hello everyone, السلام عليكم و رحمة الله و بركاته

In TypeScript, interfaces play a crucial role in defining the shape of objects and providing a contract for their behavior. They allow us to enforce consistency and ensure that our code adheres to a specific structure. Let's explore deeply what interfaces are.

Table of Contents

Introduction to Interfaces

I will take a real world example to explain what interfaces are. Let's say you are building a car. You have a blueprint that defines the structure of the car, such as the number of wheels, the color, the model, etc. This blueprint is like an interface in TypeScript. It defines the shape of an object and enforces certain properties that the object must have.

Creating Interfaces

To create an interface in TypeScript, you use the interface keyword followed by the name of the interface and the properties it should have. An example of an interface that defines the structure of a User object would be great to understand this concept:

interface User {
    id: number;
    name: string;
    email: string;
    age: number;
}

let user: User = {
    id: 1,
    name: "Bilel salem",
    email: "bilelsalemdev@gmail.com",
    age: 23,
};
Enter fullscreen mode Exit fullscreen mode

Optional Properties

if your user object has this code :

let user: User = {
    id: 1,
    name: "Bilel salem",
    email: "bilelsalemdev@gmail.com",
};
Enter fullscreen mode Exit fullscreen mode

It will throw an error because the age property is missing. So what if I have in my database some users that don't have an age? This is where optional properties come in. You can make a property optional by adding a ? after the property name age in the interface definition:

interface User {
    id: number;
    name: string;
    email: string;
    age?: number;
}
Enter fullscreen mode Exit fullscreen mode

Now, the age property is optional, and you can create a user object without it:

let user: User = {
    id: 1,
    name: "Bilel salem",
    email: "bilelsalemdev@gmail.com",
};
Enter fullscreen mode Exit fullscreen mode

This will not throw an error because the age property is optional.

Readonly Properties

Sometimes, you may want to create an object with properties that cannot be changed after they are set. As an example, let's say you have a Car interface with a model property that should not be changed once it is set. If you try to change the model property after it is set like this:

interface Car {
    model: string;
}

let car: Car = { model: "BMW" };
car.model = "Audi";
Enter fullscreen mode Exit fullscreen mode

this will be correct in TypeScript, but what if you want to prevent this from happening? You can make a property readonly by adding the readonly keyword before the property name in the interface definition:

interface Car {
    readonly model: string;
}

let car: Car = { model: "BMW" };
car.model = "Audi"; // Error: Cannot assign to 'model' because it is a read-only property.
Enter fullscreen mode Exit fullscreen mode

Now, if you try to change the model property after it is set, TypeScript will throw an error.

Extending Interfaces

Interfaces can be extended to create new interfaces that inherit the properties of existing interfaces. This is useful when you want to define a new interface that has all the properties of an existing interface, plus some additional properties. Here's an example of extending an interface:

interface User {
    id: number;
    name: string;
    email: string;
}

interface UserWithAge extends User {
    age: number;
}

let user: User = {
    id: 1,
    name: "Bilel salem",
    email: "bilelsalemdev@gmail.com",
};

let userWithAge: UserWithAge = {
    id: 1,
    name: "Bilel salem",
    email: "bilelsalemdev@gamail.com",
    age: 23,
};
Enter fullscreen mode Exit fullscreen mode

Implementing Interfaces

Now that you know how to create interfaces, you can use them to enforce a contract on classes. When a class implements an interface, it must provide an implementation for all the properties and methods defined in the interface. Here's an example of implementing an interface User in a class UserImpl:

interface User {
    id: number;
    name: string;
    email: string;
}

class UserImpl implements User {
    id: number;
    name: string;
    email: string;

    constructor(id: number, name: string, email: string) {
        this.id = id;
        this.name = name;
        this.email = email;
    }
}

class WrongUserImpl implements User {
    id: number;
    name: string;
    email: string;

    constructor(id: number, name: string) {
        this.id = id;
        this.name = name;
    }
}
// Error: Class 'WrongUserImpl' incorrectly implements interface 'User'. Property 'email' is missing in type 'WrongUserImpl' but required in type 'User'.
Enter fullscreen mode Exit fullscreen mode

In this example, the UserImpl class implements the User interface by providing an implementation for the id, name, and email properties. If you try to create a class that implements the User interface but does not provide an implementation for all the required properties, TypeScript will throw an error.

Private Properties

Now that you know how to enforce the classes to behave as you want, you can also enforce the access level of the properties. If you want to remove the access to age property from the user object, you can use the private keyword before the property name in the interface definition:

interface User {
    id: number;
    name: string;
    email: string;
    age: number;
}

class UserImpl implements User {
    id: number;
    name: string;
    email: string;
    private _age: number;

    constructor(id: number, name: string, email: string, age: number) {
        this.id = id;
        this.name = name;
        this.email = email;
        this._age = age;
    }
}

let user: User = new UserImpl(1, "Bilel salem", "bilelsalemdev#gmail.com", 23);
console.log(user.age);

// Error: Property 'age' is private and only accessible within class 'UserImpl'.
Enter fullscreen mode Exit fullscreen mode

So what if you want to access the age property? You can create a getter method in the class to access the private property:

interface User {
    id: number;
    name: string;
    email: string;
    age: number;
}

class UserImpl implements User {
    id: number;
    name: string;
    email: string;
    private _age: number;

    constructor(id: number, name: string, email: string, age: number) {
        this.id = id;
        this.name = name;
        this.email = email;
        this._age = age;
    }

    get age(): number {
        return this._age;
    }
}

let user: User = new UserImpl(1, "Bilel salem", "bilelsalemdev@gmail.com", 23);
console.log(user.age); // 23
Enter fullscreen mode Exit fullscreen mode

Conclusion

Interfaces are a powerful feature of TypeScript that allow you to define the shape of objects and enforce a contract on their behavior. They provide a way to ensure consistency and type safety in your code, making it easier to catch errors.

Top comments (0)