Computer scienceProgramming languagesTypeScriptData TypesThinking of types as sets of values

Interfaces

8 minutes read

In the world of TypeScript, interfaces are like handy blueprints for objects. Imagine you're making a recipe. The recipe tells you what ingredients you need and the steps to follow. Similarly, interfaces define what properties (ingredients) an object should have and what methods (steps) it should perform.

In this exploration of interfaces in TypeScript, we will delve into their syntax, usage, and advanced features. We will discover how interfaces can be employed to create flexible and scalable software solutions, emphasizing the importance of clear and well-defined communication between different parts of a TypeScript application.

Exploring interfaces

Think of an interface as a contract: a set of rules specifying the structure and behavior to which an object must adhere. Interfaces provide a way to define custom data types by declaring the shape of an object. They encapsulate the properties and methods an object should have without providing any implementation.

Interface naming conventions

Before proceeding further, we need to cover interface naming conventions. There are two types of naming conventions. The first kind of naming convention involves naming interfaces in the same way as classes. Under the second kind, interfaces are named in the same way as classes, but with the prefix I, which stands for Interface. Now let's dig further.

How to declare them?

Declare an entity an interface by adding the keyword interface before its name.

interface Rectangle {
  id: string;
  size: {
    width: number;
    height: number;
  }
}

In TypeScript, interfaces can't contain an implementation. When declaring interfaces, developers specify the properties and methods that objects implementing the interface must have. For example, in the next code snippet there's an error because the object rect doesn't have the id field.

const rect: Rectangle = {  // Property 'id' is missing, but required in type 'User'
  size: {
    width: 20,
    height: 30
  }
}

Optional and readonly properties

Two specific kinds of properties help preserve safe and clean code: optional properties and readonly ones.

  • Optional properties: If you don't need a property to be present in every object implementing an interface, you can mark it with the ? sign. Doing so renders this property optional in every implemented object.
  • Readonly properties: This kind of property won't change after the object is declared. Just put readonly before the property's name.
interface Rectangle {
  readonly id: string;
  size: {
    width: number;
    height: number;
  };
  color?: string;
}

In this example, color is optional, meaning a Rectangle object can lack this property, as in the snippet below, and id is immutable.

const rect: Rectangle = {
  id: '123',
  size: {
    width: 20,
    height: 30,
  }
}
rect.id = '122'  // Error: Cannot assign to 'id' because it is a read-only property.

Index signatures for dynamic properties

There are situations when you need to create an interface for an object, which will have numerous dynamic keys.

interface SomeInterface {
  [propertyName: string]: PropertyType;
}
  • propertyName is the name of the property, which can be any string.
  • PropertyType is the type of the property. It can be any valid TypeScript data type, including primitives, objects, or other custom types.
interface CSSButton {
  [key: string]: string
}

const btn: CSSButton = {
  border: '1px solid black',
  borderRadius: '15px',
  padding: '10px 15px'
}

For example, we need to create an interface for a button's appearance. Also, we know that CSS rules can be expanded in the future. In this case, we can add string syntax for all the CSS rules. By taking this approach, we give ourselves the freedom to enhance the object with new properties (string keys) and their corresponding values. The types and number of properties can be arbitrary.

Designing clear and concise interfaces

Interfaces should be concise and focused on a single responsibility. Avoid creating overly complex interfaces that try to cover too many scenarios. Instead, favor smaller, specific interfaces that can be combined or extended as needed. Clear interfaces simplify understanding and usage.

interface Shape {
  draw(): void;
}

interface Rectangle {
  width: number;
  height: number;
}

Implementing interfaces in classes

Any class that wants to implement an interface must specify this with the implements keyword. Besides, a class implementing an interface as a type is required to implement it fully. So the class, implementing an interface Rectangle from above, can look like this:

class RectWithArea implements Rectangle {
  id: string;
  size: { 
    width: number; 
    height: number; 
  };

  constructor(id: string, width: number, height: number) {
    this.id = id;
    this.size = {
      width: width,
      height: height
    };
  }

  getArea(): number {
    return this.size.width * this.size.height;
  }
}

Here, the class RectWithArea has an additional property — getArea — that is not defined in the interface. However, the class itself is still valid. The point of the interface is that we must implement the minimum set of necessary parameters. The rest of the parameters can be present in any number of parameters.

Extending interfaces for inheritance

Interfaces can extend other interfaces, inheriting their properties and methods while allowing additional declarations. This enables developers to create specialized interfaces without rewriting common properties and methods.

interface Person {
  name: string;
  age: number;
}

interface Employee extends Person {
  employeeId: number;
  role: string;
}

In this example, there's no need to declare name and age in the Employee interface, as it inherits these properties from the Person interface.

Differences between objects and interfaces

In TypeScript, both objects and interfaces are essential concepts, but they serve different purposes and have distinct characteristics.

Objects

  • Objects are instances: An object is an instance of a class or a data structure that holds key-value pairs. It represents a concrete instance of a real-world entity or a data structure in your program.

  • Dynamic properties: Objects in JavaScript and TypeScript can have dynamic properties. You can add, modify, or delete properties at runtime, making them flexible for representing various data structures.

  • Literal syntax: Objects can be created using literal syntax, like { key: value }, making them straightforward to define.

Interfaces

  • Blueprints for structure: Interfaces are TypeScript's way of defining a blueprint for the structure of objects. They define the properties and types that an object must have. Interfaces do not exist in the compiled JavaScript code; they are purely a TypeScript concept for type checking during development.

  • Static definition: Once an interface is defined, objects that implement the interface must adhere to its structure. Interfaces provide a way to enforce a specific shape for objects at compile time.

  • Reusability: Interfaces promote reusability by allowing multiple objects or classes to adhere to a common structure. If multiple objects share similar properties, those properties can be defined in an interface, ensuring consistency.

For example:

const person: { name: string; age: number } = {.    // Object of a person
  name: "Alice",
  age: 30
};

interface Person {             // Interface Person
  name: string;
  age: number;
}

const person: Person = {       // Object of the type Person
  name: "Alice",
  age: 30
};

When to use interfaces vs. types

If you've already learned about Type Aliases, now you may be wondering if both interface and type can define object shapes, so how should you choose between them?

interface UserPass {
  id: number;
  name: string;
  getPassword() {
    return `${name}${id}`
  }
}

type PersonInfo = { 
  id: number;
  name: number; 
};

A general rule is to use interfaces when you need to declare a contract that other objects can implement or extend. Use types for unions, intersections, and primitive type aliases, which simplify our code and ensure that our data structure and functions match the intended pattern. This distinction ensures clarity in your codebase.

Conclusion

Interfaces in TypeScript — because of their ability to be extended by other interfaces and implemented in classes — enhance the structure and predictability of code. They provide clear blueprints for objects, ensuring that different parts of a program communicate effectively. By defining properties, methods, and even dynamic keys, interfaces promote consistency, collaboration and error prevention. They find practical application in scenarios like API integration, UI components, and collaborative development. Guided by its capabilities, we get intuitive, versatile interfaces that are a pleasure to work with in real-world projects.

5 learners liked this piece of theory. 0 didn't like it. What about you?
Report a typo