Intermediate

TypeScript Foundations Object-oriented Programming in TypeScript

A static member (property or method) of a class is accessible independently of a specific instance. It is shared by the class itself, not by its instances.

Table of Contents

  1. Working with Classes
  1. Inheritance and Abstraction
  1. Summary of key concepts

1. Working with Classes

1.1 Setting up the demo application

Project context

This training uses the fictitious brand GloboTicket, a company that sells event tickets. The demo application allows you to explore the concepts of object-oriented programming (OOP) in TypeScript in a concrete and realistic context.

Execution environment

Several options exist for running TypeScript. The choice chosen in this training is Node.js with the npm package manager. TypeScript is installed in the project if it is not globally available on the system.

The tsx package

A key package is used: tsx. This package compiles the TypeScript and executes it directly, eliminating the need for a separate compilation step. The basic command is:

npx tsx index.ts

The main file will always be index.ts.

Project configuration (package.json)

{
  "devDependencies": {
    "tsx": "^4.20.3",
    "typescript": "^5.8.3"
  }
}

Starting structure

01-setup/event.class.ts — The starting empty class:

export class Event {

}

01-setup/index.ts — The initial entry point:

import { Event } from './event.class';

console.log('Glockticket is ready.');

This minimalist code confirms that the environment is working. The import of the Event class is already in place, and a simple console.log produces the initial output.


1.2 Understanding Classes

What is a class?

Classes are the fundamental pillars of object-oriented programming. A class is a blueprint (construction plan) that defines the structure and behavior of objects.

Contents of a class

A class can contain:

  • Properties: Data specific to each instance. For example, for the Event class:

  • date — the date of the event

  • title (or name) — the name or title of the event

  • venue — information about the venue

  • Methods: functions attached to the class that perform actions. For example:

  • cancel() — cancel the event

  • reschedule() — reschedule the event

Classes and instances

The class definition is the blueprint. From this blueprint, we create instances via the instantiation process. Each instance is independent:

  • One instance can represent event A, another an event B.
  • The two instances share the same defined properties (e.g., title, date) but not the values of these properties.
  • All instances have the same methods defined in the class.

1.3 Create and instantiate classes

Syntax for defining a class

We use the keyword class followed by the name of the class. TypeScript enriches the standard ECMAScript syntax.

03-class/event.class.ts:

export class Event {
  date: Date;
  title: string;
}

Here, two properties are defined: date of type Date and title of type string. We prefix the class with export to make it importable from other files.

Instantiation and usage

03-class/index.ts:

import { Event } from './event.class';

const event = new Event();

event.title = 'Digital Cowboys';

console.log(event.title);
  • The new keyword creates an instance of the Event class.
  • The . syntax (dot syntax) allows you to access and modify properties.
  • event.title = 'Digital Cowboys' assigns the value to the property.
  • Console output confirms that the value has been assigned.

Important points

  • The class resides in its own event.class.ts file, which is a good code organization practice.
  • TypeScript (and Visual Studio Code) provides autocompletion of properties using static typing.

1.4 Class methods

Role of methods

properties (class members) are used to contain data specific to an instance. methods are there for functionality — what the class knows how to do.

Defining a method

04-method/event.class.ts:

export class Event {
  title: string;

  setTitle(t: string): void {
    this.title = t;
  }
}

Details:

  • setTitle is a method that expects an argument of type string.
  • Its return type is void — it returns nothing.
  • this refers to the current instance of the class. this.title = t assigns the title property of the current instance with the argument received.

Method usage

04-method/index.ts:

import { Event } from './event.class';

const event = new Event();

event.setTitle('Container Enthusiasm');

console.log(event.title);

Instead of directly accessing the title property, we use the setTitle method. TypeScript and modern IDEs offer autocompletion on available methods. The console output confirms that the method has set the property.


1.5 The constructor

What is a constructor?

The constructor is a special type of method in a class. It is automatically called when instantiating the class with new. It is this which allows the properties to be initialized upon creation of the object.

Syntax

05-constructor/event.class.ts:

export class Event {
  title: string;

  constructor(t: string) {
    this.title = t;
  }
}

Key points:

  • The name is fixed: constructor.
  • It has no return type — it always returns the instance of the class, which is why you cannot (and should not) specify one for it.
  • During new Event('Digital Cowboys'), parentheses allow arguments to be passed to the constructor.

Usage

05-constructor/index.ts:

import { Event } from './event.class';

const event = new Event('Digital Cowboys');

console.log(event.title);

new Event('Digital Cowboys') calls the constructor with t = 'Digital Cowboys', which assigns this.title = 'Digital Cowboys'. The constructor here replaces the role of setTitle while being more concise and initializing the object immediately.


1.6 Overloading constructors

Why overload a constructor?

In certain scenarios, you may want to instantiate a class in different ways depending on the available data. Constructor overloading allows you to have several signatures for the same constructor.

Principle

We can define a constructor which accepts:

  • Only a title (when the date is not yet known)
  • A date and a title (when both are available)

Syntax of overloading

06-overload/event.class.ts:

export class Event {
  title: string;
  date: Date;

  constructor(t: string);
  constructor(d: Date, t: string);
  constructor(td: string | Date, t: string = '') {
    this.title = (typeof(td) === 'string') ? td : t;
    this.date = (td instanceof Date) ? td : new Date();
  }
}

Analysis:

  • The first two lines are the overload signatures. They define the accepted forms.
  • The third line is the actual implementation, which should cover all cases.
  • The first td parameter is of type string | Date (union type).
  • If td is a string, this is the title. Otherwise, the title is in t.
  • If td is an instance of Date, this is the date. Otherwise, we use new Date().

Usage

06-overload/index.ts:

import { Event } from './event.class';

let events:Event[] = [];

events.push(new Event('Digital Cowboys'));

events.push(new Event(new Date(2027, 5, 13), 'Container Enthusiasm'));

events.forEach(event => 
    console.log(`${event.title} @ ${event.date.toDateString()}`));
  • The first Event only has a title — the date is initialized to new Date() (today).
  • The second Event has a specific date (June 13, 2027) and a title.
  • Both forms are valid and TypeScript compiles without errors.

1.7 Parameter Properties

The problem

When you have a class with properties to initialize in a constructor, the code can become repetitive: you declare the property, then you assign it in the constructor.

The solution: Parameter Properties

TypeScript offers a shortcut syntax called parameter properties that merges the property declaration and assignment in the constructor into a single line.

07-parameterproperty/event.class.ts:

export class Event {

  constructor(public title: string) {
  }

}

By adding an access modifier (like public) in front of the constructor parameter, TypeScript:

  1. Automatically creates a title property on the class.
  2. Automatically assigns the parameter value to this property.
  3. The constructor body remains empty — everything is handled implicitly.

Usage

07-parameterproperty/index.ts:

import { Event } from './event.class';

const event = new Event('Network Firewall Squad');

console.log(event.title);

Property instantiation and access works exactly as before, but with much more concise class code. The next module explores access modifiers (public, private, protected) in detail.


2. Inheritance and Abstraction

2.1 Understanding inheritance

Beyond simple classes

Module 1 covered how to configure classes with properties and methods. inheritance (inheritance) allows you to go further: a class can be derived from an existing class, inheriting its properties and methods while adding its own.

A concrete example: GloboTicket

In the context of the GloboTicket platform, we think about event types from the perspective of a ticket buyer.

The Event class (generic) contains:

  • date — event date
  • venue — location of the event
  • cancel() — method to cancel a ticket (flexible or insurance ticket)

But a concert is a specific type of event with an additional property: artist. A concert also has a different business rule for cancellation (concert tickets are often non-refundable).

The class hierarchy

Event (classe de base)
├── Concert (classe dérivée — ajoute: artist)
└── Tour    (classe dérivée — ajoute: reschedule())

When Concert inherits from Event:

  • Concert automatically inherits date, venue and cancel().
  • Concert adds its own artist property.
  • Concert can override cancel() with its own logic.

Likewise for Tour:

  • Tour inherits date, venue and cancel().
  • Tour adds its own reschedule() method.

Inheritance avoids code duplication: common properties are defined only once in the base class.


2.2 Inheritance in TypeScript

The keyword extends

In TypeScript, we use the extends keyword to create a derived class (child) from a base class (parent).

02-inheritance/event.class.ts:

export class Event {
  
  date: Date;
  venue: string;
  
}

02-inheritance/concert.class.ts:

import { Event } from './event.class';

export class Concert extends Event {
  
  artist: string;
  
}

The Concert class:

  • Inherits from date and venue (defined in Event)
  • Add own artist property
  • Result: Concert has three properties — date, venue, artist

Usage

02-inheritance/index.ts:

import { Concert } from './concert.class';

const concert = new Concert();

concert.artist = 'Network Firewall Squad';
concert.venue = 'Olympic Stadium';

console.log(concert.artist, '@', concert.venue);

Both artist (added property) and venue (inherited property) can be accessed via the same instance. The console output displays both values.


2.3 Using super()

The concept of superclass

When Concert is derived from Event, Event is called the superclass or base class. In the class hierarchy, she is “above”.

Why call super()?

If the base class has a constructor, the derived class must be able to call it to ensure that initialization of the base class is done. This is the role of super().

03-super/event.class.ts:

export class Event {
  
  date: Date;
  venue: string;

  constructor() {
    console.log('Event created.');
  }
  
}

03-super/concert.class.ts:

import { Event } from './event.class';

export class Concert extends Event {
  
  artist: string;

  constructor(artist: string) {
    super();

    this.artist = artist;
  }
  
}

TypeScript rule: super() must be called before any access to this in the constructor of a derived class. If we try to access this.artist before super(), TypeScript throws an error: “super must be called before accessing this in the constructor of a derived class”.

Visual Studio Code also immediately reports if super() is missing in the constructor of a derived class.

Usage

03-super/index.ts:

import { Concert } from './concert.class';

const concert = new Concert('Digital Cowboys');

concert.venue = 'Pluralsight Arena';

console.log(concert.artist, '@', concert.venue);

During new Concert('Digital Cowboys'):

  1. The Concert constructor is called.
  2. super() calls the Event constructor → displays “Event created.”
  3. this.artist = artist affects the artist.

2.4 Method overriding

Why overload a method?

By default, a derived class inherits the implementation of methods from the base class. But sometimes we want a different implementation in the derived class. This is method overriding.

04-overriding/event.class.ts:

export class Event {
  
  date: Date;
  title: string;
  venue: string;

  toDisplayName(): string {
    return this.title;
  }
  
}

The toDisplayName() method simply returns the title of the event — useful for displaying on printed tickets or the website.

Overloading in derived class

04-overriding/concert.class.ts:

import { Event } from './event.class';

export class Concert extends Event {
  
  artist: string;

  toDisplayName(): string {
    if (this.artist === '' || 
        this.artist === undefined) {
      return super.toDisplayName();
    } else {
      return this.artist + ' @ ' +
             this.venue;
    }
  }
  
}
  • The toDisplayName() method is redefined in Concert.
  • If artist is absent (multi-artist concert, for example), we delegate to the implementation of the base class via super.toDisplayName(). This avoids code duplication.
  • Otherwise, we return an enriched representation: "Artist @ Location".
  • super here refers to the base class in the context of the current instance.

Usage

04-overriding/index.ts:

import { Concert } from './concert.class';

const concert = new Concert();

concert.title = 'Pluralsight Summit';
//concert.artist = 'Digital Cowboys';
concert.venue = 'Pluralsight Arena';

console.log(concert.toDisplayName());

Here, artist is commented out — so toDisplayName() of Concert delegates to super.toDisplayName() and simply returns 'Pluralsight Summit'. By uncommenting the artist line, the result would be "Digital Cowboys @ Pluralsight Arena".


2.5 Access Modifiers

Why control access?

access modifiers allow you to control who can access a property or method. This is a fundamental principle of encapsulation in OOP.

public — universal access

public is the default modifier. A public property or method is accessible by anyone, from anywhere. It can be written explicitly to make the code more readable.

private — restricted access to the class

A private property or method is only accessible from within the same class. Derived classes and external code do not have access to it.

protected — the happy medium

protected offers an intermediate level. A protected property or method is accessible:

  • In the class that defines it.
  • In all classes that inherit from it (derived classes).

It is not accessible from the outside (class consumer code).

readonly

The readonly modifier (often combined with public or private) prevents reassignment after initialization.

05-accessmodifiers/event.class.ts:

export class Event {
  
  //public readonly date: Date;
  private date: Date;
  protected title: string;
  protected venue: string;

  constructor() {
    this.date = new Date();
  }

}
  • date is private: only code in Event can access it. Derived classes and external code do not see this property.
  • title and venue are protected: accessible in Event and in Concert (which inherits from Event), but not from index.ts.

05-accessmodifiers/concert.class.ts:

import { Event } from './event.class';

export class Concert extends Event {
  
  private artist: string;

  constructor(artist: string, venue: string) {
    super();

    this.artist = artist;
    this.venue = venue;
  }

}
  • artist is private in Concert.
  • venue is accessible because it is protected in Event (accessible in derived classes).

05-accessmodifiers/index.ts:

import { Concert } from './concert.class';

const concert = new Concert(
    'Network Firewall Squad',
    'Pluralsight HQ');

From index.ts, we cannot access concert.date (private) nor concert.title or concert.venue (protected). TypeScript throws an error if you try.

Summary table

ModifierSame classDerived classesExterior
public
protected
private

2.6 Static Class Members

Static properties and methods

A static member (property or method) of a class is accessible independently of a specific instance. It is shared by the class itself, not by its instances.

Example with an enum

06-static/eventtype.enum.ts:

export enum EventType {
  Concert,
  Tour
}

06-static/event.class.ts:

export class Event {

  constructor() {
    console.log('Event created.');
  }

}

06-static/concert.class.ts:

import { Event } from './event.class';
import { EventType } from './eventtype.enum';

export class Concert extends Event {
  
  static readonly type: EventType = EventType.Concert;

}
  • type is static: it does not belong to an instance, but to the class itself.
  • readonly: value cannot be changed after initialization — a concert will always be a concert.

Access via class name

06-static/index.ts:

import { Concert } from './concert.class';
import { EventType } from './eventtype.enum';

let concert = new Concert;
//console.log(EventType[concert.type]);
console.log(EventType[Concert.type]);

We cannot access a static member via an instance (concert.type generates an error). We must use the class name: Concert.type. TypeScript also reports: “Did you mean to access the static member ‘Concert.type’ instead?”

Parentheses in new Concert are optional in the absence of arguments, but this is a matter of style.


2.7 Interfaces

The role of interfaces

Until now, base classes had content (properties, method implementations). Sometimes, we want to define only the structure — the blueprint — without any implementation. This is the role of interfaces.

Define an interface

07-interfaces/ievent.class.ts:

export interface IEvent {
  cancel(): boolean;
}
  • Naming convention: the I prefix indicates that it is an interface (optional but recommended).
  • The IEvent interface declares that a cancel() method returning a boolean must be implemented. There is no implementation.

Implement an interface

We use the keyword implements (and not extends) to link a class to an interface.

07-interfaces/concert.class.ts:

import { IEvent } from './ievent.class';

export class Concert implements IEvent {
  
  cancel(): boolean {
    console.error('Concert tickets are non-refundable!');
    return false;
  }

}
  • Concert implements IEvent requires that Concert provides an implementation of cancel().
  • If cancel() is absent, TypeScript throws an error: “Class ‘Concert’ incorrectly implements interface ‘IEvent’. Property ‘cancel’ is missing in type ‘Concert’ but required in type ‘IEvent’”.

Usage

07-interfaces/index.ts:

import { Concert } from './concert.class';

let concert = new Concert;
concert.cancel();

Interfaces vs base classes

AppearanceInterfaceBase class
Implementation❌ Banned✅ Possible
Keywordimplementsextends
Direct instantiation
Multiple inheritance✅ (a class can implement multiple interfaces)❌ (only one base class)

2.8 Abstract Classes

The best of both worlds?

Interfaces cannot have an implementation. Ordinary base classes always have an implementation. abstract classes offer something in between: they can have methods with an implementation and abstract methods (without implementation) that derived classes must implement.

Define an abstract class

08-abstract/event.class.ts:

export abstract class Event {
  abstract cancel(): boolean;

  constructor(protected title: string) {}

  protected toDisplayName(): string {
    return this.title;
  }
}
  • The abstract keyword before class declares the abstract class.
  • abstract cancel(): boolean; declares an abstract method: any derived class must implement it.
  • The constructor is concrete (with property protected title parameter).
  • toDisplayName() is a concrete protected method: accessible to derived classes, with its implementation provided.
  • An abstract class cannot be instantiated directly (new Event() is prohibited).

Implement an abstract class

08-abstract/concert.class.ts:

import { Event } from './event.class';

export class Concert extends Event {
  
  cancel(): boolean {
    const displayName: string = this.toDisplayName();
    console.error(`Concert tickets for "${displayName}" are non-refundable!`);
    return false;
  }

}
  • Concert extends Event — we use extends, not implements, because it is still a class.
  • cancel() is implemented (required — otherwise TypeScript throws an error).
  • Method cancel() calls this.toDisplayName() — accessible because it is protected in the abstract class.

Usage

08-abstract/index.ts:

import { Concert } from './concert.class';

let concert = new Concert('Network Firewall Squad');
concert.cancel();

Output: Concert tickets for "Network Firewall Squad" are non-refundable!

Abstract class vs Interface

AppearanceAbstract ClassInterface
Keyword derivationextendsimplements
Concrete implementations✅ Possible❌ Banned
Methods without implementation✅ (abstract)✅ (all)
Direct instantiation
Multiple inheritance

3. Summary of key concepts

The pillars of OOP in TypeScript

ConceptKeywordDescription
ClassclassBlueprint for creating objects with properties and methods
InstantiationnewCreate an instance of a class
LegacyextendsDerive one class from another
Interfaceinterface / implementsDefine a contract without implementation
Abstract classabstract classNon-instantiable class with concrete and abstract methods
Superclasssuper()Call the constructor or methods of the base class
Method overloading(redefinition)Override inherited implementation in derived class

Access Modifiers

ModifierAccessibility
publicEverywhere (default)
protectedClass + derived classes
privateClass only
readonlyRead only after initialization
staticVia class name, without instance

Specific TypeScript syntaxes

// Parameter property — déclare ET initialise en une ligne
constructor(public title: string) {}

// Constructor overloading
constructor(t: string);
constructor(d: Date, t: string);
constructor(td: string | Date, t: string = '') { /* impl */ }

// Abstract class
abstract class Event {
  abstract cancel(): boolean;  // méthode à implémenter
  toDisplayName(): string { return this.title; }  // méthode concrète
}

// Interface
interface IEvent {
  cancel(): boolean;
}

// Enum
enum EventType {
  Concert,
  Tour
}

// Static readonly
static readonly type: EventType = EventType.Concert;

GloboTicket demo hierarchy

Event (classe de base / abstract class)
├── Propriétés : date, title, venue
├── Méthodes : toDisplayName(), cancel() (abstract)
│
├── Concert (extends Event, implements IEvent)
│   ├── Propriétés héritées : date, title, venue
│   ├── Propriétés ajoutées : artist
│   ├── static readonly type: EventType.Concert
│   └── cancel() : "Concert tickets are non-refundable!"
│
└── Tour (extends Event)
    ├── Propriétés héritées : date, venue
    └── Méthodes ajoutées : reschedule()

Evolution flow of the Event class

StepDemoAddition
Empty class01-setupexport class Event {}
Properties03-classdate: Date, title: string
Method04-methodsetTitle(t: string): void
Constructor05-constructorconstructor(t: string)
Overload06-overloadTwo constructor signatures
Parameter property07-parameterpropertyconstructor(public title: string)
Legacy02-inheritanceConcert extends Event
super()03-superCall to base constructor
Overriding04-overridingRedefining toDisplayName()
Access modifiers05-accessmodifiersprivate, protected
Static06-staticstatic readonly type + enum
Interface07-interfacesConcert implements IEvent
Abstract08-abstractabstract class Event

Search Terms

typescript · foundations · object-oriented · programming · react · frontend · development · class · usage · classes · access · abstract · method · constructor · inheritance · interface · interfaces · methods · overloading · properties · syntax · define · defining · globoticket

Interested in this course?

Contact us to book it or get a custom training plan for your team.