Classes page

Learn to use classes and inheritance in TypeScript, about the constructor method, and how to use public, private, protected, and readonly modifiers.

Overview

For those newer to Object-oriented programming, classes are special functions that help us abstract our code. Classes can define function expressions and function declarations. This section will cover:

  • Using classes in TypeScript,
  • The Constructor method,
  • How to inherit classes,
  • The use of public, private, protected, and readonly modifiers, and
  • How to manage this in classes.

Classes in JavaScript

In ECMAScript 2015, classes are available as syntactic sugar over the existing prototype-based constructor functions. A simple class might look like:

class ParkEmployee {
  constructor(name){
    this.name = name;
  }
  sayHi() {
    console.log("Hi, my name is " + this.name);
  }
}

let raptorGuy = new ParkEmployee("Owen");
raptorGuy.sayHi();
//Logs "Hi, my name is Owen"

For more information on JavaScript classes, checkout the Advanced JavaScript Classes Training. The next sections will cover features TypeScript adds to JavaScript.

Classes in TypeScript

Classes in TypeScript look just like classes in JavaScript; however, there are additional features that add type safety.

In the following TypeScript class example, the name member is defined on line 3. We'll look at setting the name via the constructor next.

//class way
class ParkEmployee {
  name: string;
  constructor(name) {
    this.name = name;
  }

  sayHi() {
    console.log(`Hi, my name is ${this.name}`);
  }

}

let raptorGuy = new ParkEmployee('Owen');
raptorGuy.sayHi();
//Logs "Hi, my name is Owen"

Constructor

The constructor method is how to initialize a new object with members. The constructor is called when we instantiate a new object from calling a class with the new keyword - it constructs and returns a new object for us with properties we gave it.

class Dinosaur {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
}

let dino = new Dinosaur('Billy');
console.log(dino.name);
//Logs "Billy"

When declaring members, it's also possible to instantiate a value on them.

class Dinosaur {
  name: string;
  age = 0;
  constructor(name: string) {
    this.name = name;
  }
}

let dino = new Dinosaur('Billy');
console.log(dino.age);
//Logs "0"

Using the constructor to set public members is quite a common pattern, which is why TypeScript also provides a shorthand.

class Dinosaur {
  constructor(public name: string) {}
}
let dino = new Dinosaur('Billy');
console.log(dino.name);
// Logs "Billy"

Note: We will see how to create private members later.

Inheritance

Inheritance is a way to extend functionality of existing classes. If the derived class contains its own constructor function, it MUST call a super method with params matching that of its parent class. Super is a call to the parent constructor method to ensure the properties are set for the parent. The following shows accessing the move method from the parent class and adding run and talk methods to the child class.

class Dinosaur {
  constructor(public name: string) {}

  move(distanceInFeet: number = 0): void {
    console.log(`${this.name} moved ${distanceInFeet} feet.`);
  }
}

class Velociraptor extends Dinosaur {
  constructor(name: string, public speed: number) {
    super(name);
  }
  run(): void {
    console.log(`${this.name} runs at ${this.speed}mph.`);
  }
  talk(): void {
    console.log(`${this.name} screeches.`);
  }
}

let blue = new Velociraptor('Blue', 55);
blue.move(10);
//Logs "Blue moved 10 feet."
blue.talk();
//Logs "Blue screeches."
blue.run();
//Logs "Blue runs at 55mph."

Statics

When you need a property to be shared across multiple instances, you can use a static property. These are shared by all instances of the class as well as inheriting classes. Both members and methods on a class can be static. Each instance accesses the static value through prepending the name of the class.

This example shows the use of a static property cageInstances to count the number of instances of DinoCage:

class DinoCage {
  static cageInstances = 0;
  constructor() {
    DinoCage.cageInstances++;
  }
}

var paddock1 = new DinoCage();
var paddock2 = new DinoCage();
console.log(DinoCage.cageInstances);
//Logs "2"

Public modifier

In TypeScript all members are public by default, meaning they are publicly accessible.

class Dinosaur {
  //will be public by default
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  public walk(distanceInFeet: number): void {
    console.log(`${this.name} walked ${distanceInFeet} feet.`);
  }
}

let myDino = new Dinosaur('Mildred');
console.log(myDino.name);
//Logs "Mildred"
myDino.walk(7);
//Logs "Mildred walked 7 feet."

Private modifier

Members marked private are unable to be accessed from outside their containing class.

class Dinosaur {
  public name: string;
  private dna: string;
  constructor(name: string, dna: string) {
    this.name = name;
    this.dna = dna;
  }
  public walk(distanceInFeet: number): void {
    console.log(`${this.name} walked ${distanceInFeet} feet.`);
  }
}

let scaryDino = new Dinosaur('Indominous', 'cuttlefish');
scaryDino.dna;
//Property 'dna' is private and only accessible within class 'Dinosaur'.

Protected modifier

Protected modifiers are similar to private modifiers in that they can't be accessed but they CAN be accessed by deriving classes. The following example shows an inherited class that can access it's parent protected property teethCount:

class Dinosaur {
  public name: string;
  private dna: string;
  protected teethCount: number;
}

// EFFECT ON INSTANCES
var indominusRex = new Dinosaur();
indominusRex.name; // okay
indominusRex.dna; // ERROR : private
indominusRex.teethCount; // ERROR : protected

// EFFECT ON CHILD CLASSES
class GeneticallyModifiedDinosaur extends Dinosaur {
  constructor() {
    super();
    this.name; // okay
    this.dna; // ERROR: private
    this.teethCount; // okay
  }
}

Readonly modifier

Readonly modifiers allow properties to be read, but not changed after initialization. Read-only members can be accessed outside the class, but their value can't be changed.

class Leoplurodon {
  readonly location: string;
  readonly numberOfFlippers = 4;
  readonly magic = true;
  constructor (theLocation: string) {
    this.location = theLocation;
  }

  updateLocation(location: string): void {
    this.location = location; // Cannot assign to 'location' because it is a read-only property.ts(2540)
  }
}
let firstStop = new Leoplurodon("On the way to Candy Mountain");
firstStop.location = "On a bridge"; // error! location is readonly.

This and => Functions

If you're familiar with ES6, you may know that using the fat arrow (=>) captures the context of this where it's used. The functionality is the same in TypeScript.

Wrong this:

class DinoBuilder {
  dinoName = 'Trex';
  yawn() {
    setTimeout(function() {
      console.log(`${this.dinoName} yawned.`);
    }, 50);
  }
}

var dino = new DinoBuilder();
dino.yawn();
// Logs "undefined yawned"

Right this:

class DinoBuilder {
  dinoName = 'Trex';
  yawn() {
    setTimeout(() => {
      console.log(`${this.dinoName} yawned.`);
    }, 50);
  }
}

var dino = new DinoBuilder();
dino.yawn();
// Logs "Trex yawned"

Wrong this:

class DinoBuilder {
  dinoName = 'Trex';
  roar() {
    console.log(`${this.dinoName} roared.`);
  }
}

var dino = new DinoBuilder();

setTimeout(dino.roar, 50);
// Logs "undefined roared"

Right this:

class DinoBuilder {
  dinoName = 'Trex';
  roar = () => {
    console.log(`${this.dinoName} roared.`);
  }
}

var dino = new DinoBuilder();

setTimeout(dino.roar, 50);
// Logs "Trex roared"

Exercise: Create a Class

The Problem

In this exercise, we will take an old-school JavaScript class and convert it to a shiny new TypeScript class.

Edit the file 4a-classes-hello-dino.ts to recreate the DinoKeeper using TypeScript classes.

function DinoKeeper(name) {
  this.name = name;
}

DinoKeeper.prototype.sayHi = function() {
  return this.name + ' says "hi"';
}

let employee1 = new DinoKeeper('Joe');
employee1.sayHi();
//Joe says "hi"

Hint

If you run:

tsc 4a-classes-hello-dino.ts

Your code should transpile to look like the above prototype version! Delete the compiled js file before running the tests in the next step.

Verify your solution

✏️ Run the following to verify your solution:

npm run 4a-classes

The solution

Solution

✏️ Update 4a-classes-hello-dino.ts to the following:

class DinoKeeper {
  name: string;

  constructor(name: string) {
    this.name = name;
  }

  sayHi(): string {
    return `${this.name} says "hi"`;
  }
}
let employee1 = new DinoKeeper('Joe');
employee1.sayHi();
//Logs "Joe says "hi""

export default DinoKeeper;

Exercise: Extend a Class

The Problem

Edit 4b-specialist.ts to write a new Specialist class:

import DinoKeeper from "./4a-classes-hello-dino";

class Specialist {

}
export default Specialist;

Specialist should:

  • Inherit from DinoKeeper. This new class should
  • Accept an additional experience public member that is a number
  • Have a safetyQuote method that returns "Never turn your back to the cage. Trust me, I have ${experience} years of experience".

For example, you should be able to use Specialist as folows:

let employee2 = new Specialist('Owen', 14);
employee2.sayHi(); //Owen says 'hi'
employee2.safetyQuote();
//Logs "Never turn your back to the cage. Trust me, I have 14 years of experience"

Verify Your Solution

✏️ Run the following to verify the solution:

npm run 4b-classes

The Solution

Click to see the solution

✏️ Update 4b-specialist.ts to the following:

import DinoKeeper from "./4a-classes-hello-dino";

class Specialist extends DinoKeeper {
  constructor(name: string, public experience: number) {
    super(name);
  }

  safetyQuote() {
    return `Never turn your back to the cage.
    Trust me, I have ${this.experience} years of experience`;
  }
}
export default Specialist;