Interfaces page

Learn how to write Interfaces, set optional properties, and about the power of using interfaces in classes and functions

Overview

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. 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 simply used for typechecking 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.log(dino.location);
}

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

Optional Properties

Some times all properties on an object don’t need to be required, using the ? lets us tell the TypeScript compiler which properties aren’t required.

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

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

let 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 ClonedDino implements Dinosaur {
  name: string;
  breed: string;
  height?: number;
  location: string;
  roar(): void {
    console.log('roar');
  };
}

Interfaces in Functions

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;
}

let dinoA = {
  name: 'Blue',
  breed: 'Velociraptor',
  location: 'Section B'
};

let dinoB = {
  name: 'Sally',
  location: 'Section C'
};

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

dinoCatcher(dinoA);
//works!
dinoCatcher(dinoB);
//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; }'.

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;
}

let dinoMaker : DinoDNAMixer = function (dino1: string, dino2: string, spliceIdx: number): string {
  return dino1.substring(spliceIdx) + dino2.substring(spliceIdx);
}

let newDino = dinoMaker('CGGCAD', 'ACGCAA', 3);
console.log(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;

interface DinoFactory {
  makeDino: DinoDNAMixer;
  factoryName: string;
}

Type Assertion

We briefly mentioned type assertion when talk about types, but when dealing with interfaces it can be a great tool for making sure our code behaves in the way we expect.

For instance, consider the following code:

var myObj = {};
myObj.name = 'Blue';
//property 'name' does not exist on type '{}'
myObj.height = 6;
//property 'height' does not exist on type '{}'

When we create empty object literals in TypeScript, they are inferred to be objects with zero properties. To fix this, we can use type assertions to let the compiler explicitly know what we want from our object.

interface Dinosaur {
  name: string;
  height: number;
}

var myObj = {} as Dinosaur;
myObj.name = 'Blue';
myObj.height = 6;

Exercise 1: Write Interfaces to Describe an Object

The Problem

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

let 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)

The solution

Click to see the solution

✏️ Update 5a-dino-park.ts to the following:

import Address from "./5a-address";

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

export default DinoPark;

✏️ Update 5a-address.ts to the following:

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

export default Address;

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

Exercise 2: Write a Function With a Parameter Described by Interface

The Problem

In the 5b-create-park-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. Ex. the park "Isla Sorna Park" should return the slug Isla-Sorna-Park.

Verify Your Solution

✏️ Run the following to verify your solution:

npm run 5-interfaces

The solution

Click to see the solution

✏️ Update 5b-create-park-slug.ts to the following:

import DinoPark from "./5a-dino-park";

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