Interfaces page

Learn how to write Interfaces, set optional properties, and use interfaces in classes and functions.

Overview

In this section, you will:

  • Write interfaces.
  • Set optional properties.
  • Use the power of interfaces in classes and functions.

Objective 1: Define interfaces for objects

Interfaces are a powerful way to enforce types and document what our code provides. Interfaces used in classes allow "loose coupling" while providing a shape. Multiple classes can use interfaces in many different ways. This section will cover how to write interfaces, setting optional properties, and the power of using interfaces in classes and functions.

Interfaces in TypeScript

An interface in TypeScript is a way to define the shape an entity should adhere to. An interface defines the members: properties, methods, and events. It may be easy to think of it as the signature of an API. It’s worth noting that interfaces aren’t transpiled into our output JavaScript; they’re only used for type-checking during the development process.

// Interface describing object
interface Dinosaur {
  name: string;
  breed: string;
  height: number;
  location: string;
}

// Function with interface describing parameter
function trackDino(dino: Dinosaur) {
  console.info(dino.location);
}

const blue = {
  name: "blue",
  breed: "Velociraptor",
  height: 7,
  location: "Section B",
};
trackDino(blue);
// Logs "Section B"

Interfaces in function parameters

Interfaces are incredibly useful in describing the shape of objects we want to use in multiple situations. The following functions both require a Dinosaur object shape we’ve defined in the Dinosaur interface.

interface Dinosaur {
  name: string;
  breed: string;
  location: string;
}

const dinoA = {
  name: "Blue",
  breed: "Velociraptor",
  location: "Section B",
};

const dinoB = {
  name: "Sally",
  location: "Section C",
};

function dinoCatcher(dinosaur: Dinosaur) {
  console.info(`Caught ${dinosaur.name} at ${dinosaur.location}`);
}

dinoCatcher(dinoA);
// Works!

dinoCatcher(dinoB);
// Error: Argument of type '{ name: string; location: string; }' is not assignable to parameter of type 'Dinosaur'.
// Property 'breed' is missing in type '{ name: string; location: string; }'.

Optional properties

Sometimes all properties on an object don’t need to be required, so using the ? tells the TypeScript compiler which properties aren’t required.

interface Dinosaur {
  name: string;
  breed: string;
  height?: number;
  location: string;
}

function trackDino(dino: Dinosaur) {
  console.info(dino.location);
}

const blue = {
  name: "blue",
  breed: "Velociraptor",
  location: "Section B",
};
// Works
trackDino(blue);
// Logs "Section B"

Classes implementing interfaces

In the case that a class needs to follow an object structure, we can use interfaces to define that 'contract'.

interface Dinosaur {
  name: string;
  breed: string;
  height?: number;
  location: string;
}

class DinoWithRoar implements Dinosaur {
  name: string;
  breed: string;
  height?: number;
  location: string;
  roar(): void {
    console.info("roar");
  }
}

Setup 1

✏️ Create src/interfaces/address.ts and update it to be:

interface Address {}

export default Address;

✏️ Create src/interfaces/dinoPark.ts and update it to be:

import Address from "./address";

interface DinoPark {}

export default DinoPark;

Verify 1

✏️ Create src/interfaces/address.test.ts and update it to be:

import assert from "node:assert/strict";
import { describe, it } from "node:test";
import type Address from "./address";

describe("Interfaces", () => {
  function checkAddress(address: Address) {
    var keys = Object.keys(address);
    assert.deepEqual(keys, ["street", "city", "state", "zip"]);
  }

  it("Address", () => {
    checkAddress({
      street: "123 Main",
      city: "Sandusky",
      state: "Ohio",
      zip: "12345",
    });
  });
});

✏️ Create src/interfaces/dinoPark.test.ts and update it to be:

import assert from "node:assert/strict";
import { describe, it } from "node:test";
import type DinoPark from "./dinoPark";
import type Address from "./address";

describe("Interfaces", () => {
  function checkAddress(address: Address) {
    var keys = Object.keys(address);
    assert.deepEqual(keys, ["street", "city", "state", "zip"]);
  }

  it("DinoPark", () => {
    function checkFullDinoPark(dinoPark: DinoPark) {
      var keys = Object.keys(dinoPark);
      assert.deepEqual(keys, ["name", "image", "address"], "has an image");
      checkAddress(dinoPark.address);
    }

    checkFullDinoPark({
      name: "Isla Sorna Park",
      image: "http://dino.com/pic.jpg",
      address: {
        street: "123 Main",
        city: "Sandusky",
        state: "Ohio",
        zip: "12345",
      },
    });

    function checkPartialDinoPark(dinoPark: DinoPark) {
      var keys = Object.keys(dinoPark);
      assert.deepEqual(keys, ["name", "address"], "optional image");
      checkAddress(dinoPark.address);
    }
    checkPartialDinoPark({
      name: "Isla Sorna Park",
      address: {
        street: "123 Main",
        city: "Sandusky",
        state: "Ohio",
        zip: "12345",
      },
    });
  });
});

Exercise 1

We’re going to write some interfaces to set up for the next problem. Edit the files address.ts and dinoPark.ts to create an interface to define an Address and DinoPark object shown below:

const address = {
  street: "",
  city: "",
  state: "",
  zip: "",
};

const park = {
  name: "",
  image: "",
  address: {
    street: "",
    city: "",
    state: "",
    zip: "",
  },
};

Hint: the interface should have properties and types:

  • name (string)
  • image (string) (optional)
  • address
    • street (string)
    • city (string)
    • state(string)
    • zip (string)

Have issues with your local setup? You can use either StackBlitz or CodeSandbox to do this exercise in an online code editor.

Solution 1

If you’ve implemented the solution correctly, the tests will pass when you run npm run test!

Click to see the solution

✏️ Update src/interfaces/address.ts to be:

interface Address {
  street: string;
  city: string;
  state: string;
  zip: string
}

export default Address;

✏️ Update src/interfaces/dinoPark.ts to be:

import Address from "./address";

interface DinoPark {
  name: string;
  image?: string;
  address: Address;
}

export default DinoPark;

Have issues with your local setup? See the solution in StackBlitz or CodeSandbox.

Make sure you have this solution implemented correctly before moving on to the next exercise.

Objective 2: Define interfaces for functions

Interfaces describing functions

We can also use interfaces to describe functions, basically creating reusable types for functions. On the left side (in parenthesis) we list the parameters, and to the right of the colon, we state the return type.

interface DinoDNAMixer {
  (dino1: string, dino2: string, spliceIdx: number): string;
}

const dinoMaker: DinoDNAMixer = (dino1, dino2, spliceIdx) => {
  return dino1.substring(spliceIdx) + dino2.substring(spliceIdx);
};

const newDino = dinoMaker("CGGCAD", "ACGCAA", 3);
console.info(newDino); // Logs 'CADCAA'

It’s possible to use the type keyword as an interface to describe a function.

type DinoDNAMixer = (dino1: string, dino2: string, spliceIdx: number) => string;

const dinoMaker: DinoDNAMixer = (dino1, dino2, spliceIdx) => {
  return dino1.substring(spliceIdx) + dino2.substring(spliceIdx);
};

const newDino = dinoMaker("CGGCAD", "ACGCAA", 3);
console.info(newDino); // Logs 'CADCAA'

Setup 2

✏️ Create src/interfaces/slug.ts and update it to be:


export default function createParkSlug(dinoPark) {
  return dinoPark.name.toLowerCase().replace(/ /g, '-');
}

Verify 2

✏️ Create src/interfaces/slug.test.ts and update it to be:

import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { createParkSlug } from "./slug";

describe("Interfaces", () => {
  it("createParkSlug", function () {
    const result = createParkSlug({
      name: "Isla Sorna Park",
      address: {
        street: "123 Main",
        city: "Sandusky",
        state: "Ohio",
        zip: "12345",
      },
    });
    assert.equal(result, "isla-sorna-park", "slug works");
  });
});

Exercise 2

In the slug.ts file, edit the createParkSlug function to take a parameter that is the interface DinoPark created previously and returns a slug for the park by replacing any spaces with dashes. Example: the park Isla Sorna Park should return the slug Isla-Sorna-Park.

Have issues with your local setup? You can use either StackBlitz or CodeSandbox to do this exercise in an online code editor.

Solution 2

If you’ve implemented the solution correctly, the tests will pass when you run npm run test!

Click to see the solution

✏️ Update src/interfaces/slug.ts to be:

import DinoPark from "./dinoPark";

export function createParkSlug(dinoPark: DinoPark) {
  return dinoPark.name.toLowerCase().replace(/ /g, '-');
}

Have issues with your local setup? See the solution in StackBlitz or CodeSandbox.

Next steps

Next, let’s take a look at keyof and typeof to create new types from types.