Object Oriented Programming using Typescript: A Complete Guide
Classes
They are the blueprint for its instances (objects that the class produce).
JavaScript has had classes since the ES5 language implementations. In there it was introduced to us the native implementations of “classes” was. Typescript extends the functionalities of that feature to make it look more like more common programming languages like Java for example.
Before we continue, if you're in a hurry, here's the GitHub repository with all the code of this guide, just in case.
Now, let’s see what functionalities are and how to use them.
Basic declaration
There is no mystery here. You use the keyword class
, next to the name of the class (starting it with Uppercase by convention) and open brackets.
/* ------------- Basic declaration ------------ */
class A {
a1: string;
a2: number;
a3: boolean;
constructor(a1: string, a2: number, a3: boolean) {
this.a1 = a1;
this.a2 = a2;
this.a3 = a3;
}
}
In that snippet of code, you have a1
, a2
and a3
declared at the top of the class, that’s because Typescript will use that to tell this
that those are part of its constructor and will not throw an error nor syntax warnings on the editor.
I know that’s a little too much boilerplate for just a simple class, but don’t worry, keep reading and I’ll show you a shorter way of doing that.
Then you can instantiate it like this:
const aa = new A("New Class", 1, true);
console.log("Basic declaration:", aa);
With methods
Went you wanna add methods, you don’t need to declare it at the top of the class nor do you have to use the keyword function
.
/* --------------- With methods --------------- */
class B {
b1: string;
b2: number;
b3: boolean;
constructor(b1: string, b2: number, b3: boolean) {
this.b1 = b1;
this.b2 = b2;
this.b3 = b3;
}
run(time: "all day" | "just the mornings" | "anytime"): void {
console.log(`I'm running ${time}, so you have this: ${this.b1} times ${this.b2} and it's ${this.b3}`);
}
}
const bb = new B("Power", 59, true);
console.log("B class:", bb);
bb.run("all day");
Encapsulation
Sometimes you’ll want to limit the kind of information that the instances share with the rest of the code, in those cases, you’ll use encapsulation.
The default way that classes instantiate their properties is public
. Anyone can see them, anyone can change them, and anyone can manipulate that data.
Then, you have private
properties which can only be manipulated by the class itself. By convention, you start the name of a private with an underscore _
and they are strongly related to getters and setters, which I’ll explain in a moment
Some properties can only be read and cannot be modified, not even by the class itself, those are readonly
.
And finally, we have protected
which is very similar to private but with the difference that protected properties can also be modified by inherited classes (I’ll talk a little bit about that later).
/* --------------- Encapsulation -------------- */
class C {
c0: any // public by default
public c1: null | number;
private _c2: boolean | string;
readonly c3: string;
protected _c4: string;
constructor({ c1, c2, c3, c4 }: { c1: null | number, c2: boolean, c3: string, c4: string, }) {
this.c0 = "Whatever";
this.c1 = c1;
this._c2 = c2;
this.c3 = c3;
this._c4 = c4;
}
get c2() {
return `Mr. C2 says that it's value is: ${this._c2}. Now you can go, please`;
}
set c2(newValue: boolean | string) {
// this.c3 = "Nope, still not changing"; // it will change the value, but the idea is to listen to typescript
this._c2 = newValue;
}
set c4(newValue: string) {
this._c4 = newValue;
}
}
const cc = new C({ c1: null, c2: false, c3: "You can only see me, not modify me", c4: "Only the class can make me change and it's children" });
console.log("Encapsulation with class:", cc);
cc.c0 = "I'm public, whatever";
cc.c1 = 975;
// cc._c2 = true; // it will change the value, but the idea is to listen to typescript
cc.c2 = "VIP";
// cc.c3 = "Time to change... hahaha, not really"; // it will change the value, but the idea is to listen to typescript
// cc._c4 = "I told you, only the class can change me, not you"; // it will change the value, but the idea is to listen to typescript
cc.c4 = "Oh, well, if you say it please.... okey, let's go";
console.log("After changes:", cc)
console.log(cc.c2)
console.log(cc.c3)
// console.log(cc._c4) // it will display the value, but the idea is to listen to typescript
In the code above you’ll see a lot of comments, that’s because since this is an implementation of Typescript, many of these keywords don’t exist natively on Javascript, so the changes that you make in those scenarios are, even if they are invalid to Typescript, it will still run went it transpile to Javascript.
Getters and Setters
Although this is not specific to Typescript but also Javascript. Getters and Setters are used to set
or get
a value of a property.
They are useful when you are using private
or protected
properties but you still want a way for the class to expose those values without directly referencing them.
Using the example above, they would like something like this:
class C {
private _c2: boolean | string;
constructor({ c2, }: { c2: boolean }) {
this._c2 = c2;
}
get c2() {
return `Mr. C2 says that it's value is: ${this._c2}. Now you can go, please`;
}
set c2(newValue: boolean | string) {
// this.c3 = "Nope, still not changing"; // it will change the value, but the idea is to listen to typescript
this._c2 = newValue;
}
}
Get
get
will return the value of the property.
console.log(cc.c2)
Set
set
can overwrite the value of the private
property.
cc.c2 = "VIP";
Short constructor
To avoid all the boilerplate to declare the variables with their types at the beginning of the class, you can use attributes of the constructor to short that off. The only condition is to add the access type of each variable.
/* ------------- Short constructor ------------ */
class D {
constructor(
// d0: any // you need to add the type of access: public, private, etc
public d1: number,
private d2: boolean,
readonly d3: string,
protected d4: number = 888,
) { }
d5(): void {
console.log("Just logging");
}
}
const dd = new D(13, true, "Reading...", /* 99 */);
console.log("Short constructor", dd);
Inheritance
A class can have all the methods and properties that another class has. To do so, one class extends
the functionalities of another, this process is called inheritance because, in some sense, the class extended will become a “parent” and the other a “child” that will inherit everything that its “parents” has.
As a side note, a class can only inherit one class at a time.
/* ---------------- Inheritance --------------- */
class E {
constructor(
public e1: number,
private e2: string = "Hi, I'm VIP",
protected e3: string = "Hello, I'm can be used be the inheritance",
) { }
e4() {
console.log("%c Here, just doing some Class E stuff", "color: pink; background-color: black",);
}
}
class F extends E {
constructor(
e1: number, // for the super class don't put the access type (public, private, etc)
public f1: number,
) {
super(e1);
}
f2() {
console.log(`Super thing 1 ${this.e1}`);
// console.log(`Super thing 2 ${this.e2}`); // privates can be extended to the children
console.log(`Super thing 3 ${this.e3}`);
super.e4();
}
}
const ee = new E(2000);
const ff = new F(2501, 3000);
console.log(ee);
console.log(ff);
ff.f2();
// class G extends A, B, C, D, {} // it can only extend 1 class at the time
Static methods
With the static
keyword you can use properties and methods (mostly methods) without instantiating the class but with the class itself. Just keep in mind that the object instance this
will not work as expected since there will be nothing instantiated yet.
/* ------------------ Statics ----------------- */
class G {
constructor(
public gg = "Good game, gg",
) { }
static g = "gg, GG, gg";
static ggGGgg() {
console.log(`By the way, gg. ${this.gg}. Woops, I forgot that you need to initialize my first, unless... ${this.g}...`);
}
}
G.ggGGgg();
console.log(G.g)
Abstract Classes
An abstract class is the class you don’t want to instantiate because they are too open in its definition and too vague to make an object out of it. Instead, what you wanna do is to define it and let other classes inherit from it. That’s the whole point of an abstract class.
/* ----------------- Abstract ----------------- */
// when a class is too general
// and you don't want to allow instances of that class
// but you still want to inherit
// then you can use an abstract class
interface ILiving {
name: string;
exists(): void;
}
abstract class LivingThing implements ILiving { // ignore the "interface" and "implements" for now
constructor(
public name: string = "perrito",
) { }
exists(): void {
console.log("Existing");
}
}
// const livingThing = new LivingThing("Creature"); // Just don't, ok?
class Dog extends LivingThing {
constructor(
name: string,
public power: string,
) {
super(name);
}
move(): void {
console.log("Moving using 4 legs");
}
}
const dog = new Dog("Perrito", "Perfection");
console.log("Extending from abstract class", dog);
dog.exists();
dog.move();
Interfaces in classes
Interfaces are related to objects and only exist on Typescript, not in Javascript. They declare what properties and methods the object will have and what type of data it’ll manage.
interface H {
h1: number;
h2(): number;
}
Like classes, they can extend from each other. But, there is no such thing as polymorphism, which I’ll explain in another moment, but means that you can’t overwrite a property, for example h1
with another interface.
/* ---------------- Interfaces ---------------- */
interface H {
h1: number;
h2(): number;
}
interface I {
i1: string;
i2: ILiving;
}
interface J extends H, I {
j1: boolean;
}
And you use the keyword implements
to use them in your classes.
class K implements H, I, J { // in this example implementing "H" and "I" are optionals since "J" already does that
constructor(
public h1: number,
public i1: string,
public i2: ILiving,
public j1: boolean,
) {}
h2() {
return 66;
}
}
const kk = new K(44, "Welcome", dog, true);
console.log("Interfaces and classes =>", kk)
Well, there you have it. A complete guide to Object Oriented Programming using Typescript. If you have anything to add to it please comment on it or reach out to me through my social media:
Twitter, LinkedIn, Instagram, Github, Medium (For more opinion-based posts), and here on Hashnode and my Hashnode blog. Also, follow me if you want XD
Remember that here's the repository with all the code.
Have a nice day and until next time. Bye.