Skip to main content

interfaces-introduction-and-basic-usage

6. Interfaces: Introduction and Basic Usage

  • Introduce interfaces for declaring custom types and using them to enforce type conformity for objects and classes. Absolutely! Understanding interfaces is crucial for leveraging TypeScript's full capabilities. Here are 20 key points you should know about TypeScript interfaces:

What Are Interfaces?:

Interfaces in TypeScript are used to define the structure of an object. They enforce certain properties to exist on an object, and have specific types. It's like a contract for objects, classes, and even functions to follow.

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

Basic Syntax:

The basic syntax for an interface involves using the interface keyword followed by the interface's name and the definition of its properties within curly braces.

interface Person {
firstName: string;
lastName: string;
}

Optional Properties:

Interfaces can have optional properties, marked by a ? after the property name. This means that objects of this interface can be created with or without these properties.

interface Vehicle {
make: string;
model: string;
year?: number; // Optional property
}

Readonly Properties:

You can prevent modification of properties after an object is created by marking properties as readonly. This is often used for IDs or other properties that should not change after initialization.

interface Point {
readonly x: number;
readonly y: number;
}

Function Types:

Interfaces can describe function types by specifying the parameters and the return type of a function. This is useful for creating a variable that must be assigned a function with a specific signature.

interface SearchFunc {
(source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
return source.search(subString) > -1;
}

These examples should help clarify the use of interfaces in TypeScript and how they can be applied to various types of data structures and functions.

  1. Method Signatures:

    You can use interfaces to specify method signatures that classes implementing the interface must contain. This is key for ensuring conformity to certain behaviors.

interface Greeter {
greet(name: string): string;
}

class EnglishGreeter implements Greeter {
greet(name: string) {
return `Hello, ${name}`;
}
}
  1. Extending Interfaces:

    TypeScript allows interfaces to extend one another using the extends keyword. This helps in composing multiple interfaces into one.

interface Shape {
color: string;
}

interface Square extends Shape {
sideLength: number;
}

let square: Square = { color: "blue", sideLength: 10 };
  1. Implementing Interfaces in Classes:

    Classes can implement interfaces to enforce that they adhere to a specific structure. This is done using the implements keyword.

interface ClockInterface {
currentTime: Date;
setTime(d: Date): void;
}

class Clock implements ClockInterface {
currentTime: Date = new Date();
setTime(d: Date) {
this.currentTime = d;
}
}
  1. Interface Inheritance:

    Learn how one interface can inherit properties and methods from another interface, promoting reusability and modular design.

interface Shape {
color: string;
}

interface PenStroke {
penWidth: number;
}

interface Square extends Shape, PenStroke {
sideLength: number;
}

let square: Square = {
color: "red",
sideLength: 20,
penWidth: 5.0
};
  1. Generic Interfaces:

    Interfaces can have generic parameters, making them flexible enough to work with a variety of types while still enforcing a structure.

interface ResponseContainer<T> {
status: number;
response: T;
}

let stringResponse: ResponseContainer<string> = {
status: 200,
response: "Success"
};

let numberResponse: ResponseContainer<number> = {
status: 404,
response: 0
};

Indexable Types:

Interfaces can be used to describe types that allow for indexing, meaning they can have properties with dynamic names. You can specify the type of index, like a number or string, and the type of the value that the index returns.

interface StringArray {
[index: number]: string;
}

let myArray: StringArray;
myArray = ["Bob", "Fred"];

Interface as Function Types:

You can use interfaces to define the structure of a function, specifying the types of its parameters and return type. This creates a template for functions, ensuring they follow the same structure.

interface GreetFunction {
(name: string): string;
}

let greet: GreetFunction;
greet = function(name: string) {
return "Hello, " + name;
}

Type Compatibility:

In TypeScript, two types are compatible if their internal structure is compatible. This is checked by the compiler when you use interfaces to ensure the object meets the contract the interface defines, including optional properties.

interface Named {
name: string;
}

let x: Named;
// y's inferred type is { name: string; location: string; }
let y = { name: "Alice", location: "Seattle" };
x = y; // OK because 'y' has at least all of the properties 'x' has

Mapped Types with Interfaces:

Interfaces can't directly define mapped types, but you can use them in conjunction with types to map over the properties of an interface and transform them.

interface Roles {
admin: boolean;
user: boolean;
}

type NullableRoles = { [P in keyof Roles]?: Roles[P] };

Call Signatures:

Interfaces can contain call signatures, which describe functions that can be called and detail the types of their parameters and return type.

interface DescribableFunction {
description: string;
(someArg: number): boolean;
}

function doSomething(fn: DescribableFunction) {
console.log(fn.description + " returned " + fn(6));
}

These examples provide insight into more complex ways of using interfaces to structure various types and functions in TypeScript.

  1. Constructor Signatures:

    Interfaces can have constructor signatures to describe the new operation. However, you cannot implement them in a class; they are used in factory functions instead.

interface ClockConstructor {
new (hour: number, minute: number): ClockInterface;
}

interface ClockInterface {
tick(): void;
}

function createClock(ctor: ClockConstructor, hour: number, minute: number): ClockInterface {
return new ctor(hour, minute);
}

class DigitalClock implements ClockInterface {
constructor(h: number, m: number) { /* Constructor implementation */ }
tick() {
console.log("beep beep");
}
}

class AnalogClock implements ClockInterface {
constructor(h: number, m: number) { /* Constructor implementation */ }
tick() {
console.log("tick tock");
}
}

let digital = createClock(DigitalClock, 12, 17);
let analog = createClock(AnalogClock, 7, 32);
  1. Hybrid Types:

    Interfaces can describe objects that are both a function and an object, providing multiple types of operations.

interface Counter {
(start: number): string;
interval: number;
reset(): void;
}

function getCounter(): Counter {
let counter = (function (start: number) { }) as Counter;
counter.interval = 123;
counter.reset = function () { };
return counter;
}

let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
  1. Interfaces vs. Type Aliases:

    Learn the key differences between interfaces and type aliases and when to use each. While interfaces are generally more extensible, type aliases can describe more complex shapes.

// Interface
interface Point {
x: number;
y: number;
}

// Extending an interface
interface Point3D extends Point {
z: number;
}

// Type alias
type Point = {
x: number;
y: number;
};

// Intersection types with type alias
type Point3D = Point & {
z: number;
};
  1. Declaration Merging:

    Understand how interface declarations with the same name get automatically merged, effectively extending the previous declaration.

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

interface Box {
scale: number;
}

let box: Box = { height: 5, width: 6, scale: 10 };
  1. Type Assertions with Interfaces:

    Learn how to use type assertions with interfaces to guide the TypeScript compiler, particularly when you're sure about the type you're working with but TypeScript is not.

interface Bird {
fly(): void;
layEggs(): void;
}

// Assume we're confident that `getSmallPet` returns a `Bird`
let pet = getSmallPet() as Bird;
pet.fly();

function getSmallPet(): Bird | Fish {
// ...
}