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;