TypeScript Classes — Complete Guide
In this tutorial, you will learn about TypeScript Classes. We cover key concepts, practical examples, and best practices to help you master this topic.
TypeScript classes extend JavaScript ES6 classes with type annotations, access modifiers, parameter properties, and interface implementation — giving you a familiar OOP syntax with compile-time type safety.
What You'll Learn
- Class syntax with typed properties and methods
- Access modifiers: public, private, protected
- Parameter properties shorthand
readonlypropertiesimplementsfor interface conformance
Why It Matters
Classes are the foundation of object-oriented programming in TypeScript. While TypeScript also supports functional patterns, many frameworks (Angular, NestJS) and Design Patterns rely heavily on classes. Properly typed classes serve as self-documenting blueprints for your objects.
Real-World Use
NestJS (used at DodaTech for internal services) relies on classes with decorators for Dependency Injection, controllers, and services. The private and readonly modifiers ensure that service dependencies are encapsulated and never accidentally reassigned.
Learning Path
flowchart LR A[Type Manipulation] --> B[Classes] B --> C[Abstract Classes] B --> D[You Are Here] C --> E[Inheritance] E --> F[Decorators]
Class Basics
class User {
name: string;
age: number;
email: string;
constructor(name: string, age: number, email: string) {
this.name = name;
this.age = age;
this.email = email;
}
greet(): string {
return `Hello, my name is ${this.name}`;
}
isAdult(): boolean {
return this.age >= 18;
}
}
const alice = new User("Alice", 30, "alice@example.com");
console.log(alice.greet()); // Hello, my name is Alice
console.log(alice.isAdult()); // true
Think of a class as a blueprint and instances (objects created with new) as houses built from that blueprint.
Access Modifiers
public (default)
Accessible everywhere:
class Car {
public make: string = "Toyota"; // public is the default
public model: string = "Camry";
public drive(): void {
console.log("Driving...");
}
}
const car = new Car();
console.log(car.make); // OK — public
car.drive(); // OK — public
private
Accessible only within the class:
class BankAccount {
private balance: number = 0;
public deposit(amount: number): void {
if (amount > 0) {
this.balance += amount;
}
}
public getBalance(): number {
return this.balance;
}
private logTransaction(type: string, amount: number): void {
console.log(`${type}: ${amount}`);
}
}
const account = new BankAccount();
account.deposit(100);
// account.balance; // Error: Property 'balance' is private
// account.logTransaction("deposit", 100); // Error: private method
protected
Accessible within the class and subclasses:
class Animal {
protected sound: string = "";
protected makeSound(): void {
console.log(this.sound);
}
}
class Dog extends Animal {
constructor() {
super();
this.sound = "Woof!"; // OK — protected property accessible in subclass
}
public bark(): void {
this.makeSound(); // OK — protected method
}
}
const dog = new Dog();
dog.bark(); // OK — public
// dog.sound; // Error: protected
// dog.makeSound(); // Error: protected
Parameter Properties
TypeScript's shorthand for declaring and initializing class properties in the constructor:
// Verbose way
class UserVerbose {
private name: string;
public age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
// Shorthand — same thing
class UserShorthand {
constructor(
private name: string,
public age: number,
readonly email: string = "no-email",
) {}
}
const user = new UserShorthand("Alice", 30, "alice@example.com");
console.log(user.age); // 30
// console.log(user.name); // Error: private
Parameter properties save 3-4 lines per constructor parameter and are idiomatic TypeScript.
Readonly Properties
Prevent reassignment after construction:
class Config {
readonly apiKey: string;
readonly endpoint: string;
timeout: number; // Can change
constructor(apiKey: string, endpoint: string, timeout: number = 5000) {
this.apiKey = apiKey;
this.endpoint = endpoint;
this.timeout = timeout;
}
}
const config = new Config("sk-abc", "https://api.example.com");
// config.apiKey = "new-key"; // Error: Cannot assign to 'readonly' property
config.timeout = 10000; // OK — not readonly
implements
A class can implement an interface, ensuring it conforms to a contract:
interface Printable {
print(): void;
}
interface Serializable {
toJSON(): string;
}
class Report implements Printable, Serializable {
constructor(private title: string, private content: string) {}
print(): void {
console.log(`Title: ${this.title}`);
console.log(`Content: ${this.content}`);
}
toJSON(): string {
return JSON.stringify({ title: this.title, content: this.content });
}
}
const report = new Report("Scan Results", "No threats found");
report.print();
// Title: Scan Results
// Content: No threats found
Getter and Setter
class Temperature {
private _celsius: number = 0;
get celsius(): number {
return this._celsius;
}
set celsius(value: number) {
if (value < -273.15) {
throw new Error("Temperature below absolute zero");
}
this._celsius = value;
}
get fahrenheit(): number {
return (this._celsius * 9) / 5 + 32;
}
}
const temp = new Temperature();
temp.celsius = 25;
console.log(`${temp.celsius}°C = ${temp.fahrenheit}°F`);
// 25°C = 77°F
Common Mistakes
1. Forgetting to Initialize Properties
class User {
name: string; // Error with strictPropertyInitialization: not initialized
// Fix: name: string = "";
// Or: constructor(public name: string) {}
}
Enable strictPropertyInitialization: true (included in strict) to catch this.
2. Using private Instead of JavaScript # Private Fields
TypeScript's private is a compile-time check. JavaScript's # is truly private at runtime:
class TSPrivate {
private secret = "hidden";
}
class JSPrivate {
#secret = "hidden";
}
const ts = new TSPrivate();
(ts as any).secret; // Accessible at runtime via type assertion
const js = new JSPrivate();
// (js as any).#secret; // Syntax error — truly private
Prefer # for true runtime privacy; use TypeScript's private for compile-time only.
3. Not Using Parameter Properties
// 7 extra lines of boilerplate
class Verbose {
private x: number;
private y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
// 1 line
class Concise {
constructor(private x: number, private y: number) {}
}
4. Putting Type Annotations Where TypeScript Can Infer
class User {
name: string; // OK: class properties need explicit types
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
greet(): string { // Explicit return type is good practice
return `Hi, I'm ${this.name}`;
}
}
5. Confusing implements with extends
implements checks that a class satisfies an interface. extends inherits implementation from another class.
Practice Questions
What is the default access modifier in TypeScript classes?
public— all members are public unless explicitly markedprivateorprotected.What do parameter properties do? They combine parameter declaration and property initialization in the constructor:
constructor(private name: string) {}What is the difference between
privateandprotected?privatemembers are accessible only within the declaring class.protectedmembers are also accessible in subclasses.Can a class implement multiple interfaces? Yes:
class MyClass implements InterfaceA, InterfaceB {}
Challenge: Write a Vector2D class with private x and y fields, a constructor, getters, a length() method, and an add(other: Vector2D): Vector2D method. Use parameter properties.
FAQ
Mini Project: Task Manager Class
// src/task-manager.ts
interface Task {
id: string;
title: string;
completed: boolean;
createdAt: Date;
}
class TaskManager {
private tasks: Task[] = [];
constructor(private owner: string) {}
addTask(title: string): Task {
const task: Task = {
id: `task-${Date.now()}`,
title,
completed: false,
createdAt: new Date(),
};
this.tasks.push(task);
return task;
}
completeTask(id: string): boolean {
const task = this.tasks.find(t => t.id === id);
if (!task) return false;
task.completed = true;
return true;
}
getPendingTasks(): Task[] {
return this.tasks.filter(t => !t.completed);
}
getCompletedTasks(): Task[] {
return this.tasks.filter(t => t.completed);
}
get summary(): string {
return `${this.owner}: ${this.tasks.length} tasks (${this.getPendingTasks().length} pending)`;
}
}
const manager = new TaskManager("Alice");
manager.addTask("Learn TypeScript classes");
manager.addTask("Build a project");
manager.addTask("Write tests");
manager.completeTask(manager.getPendingTasks()[0].id);
console.log(manager.summary);
// Alice: 3 tasks (2 pending)
console.log(`Completed: ${manager.getCompletedTasks().length}`);
// Completed: 1
What's Next
Now explore abstract classes for defining base templates:
| Lesson | Description |
|---|---|
| {{< ref "/programming-languages/typescript/18-type-manipulation" >}} | Review type manipulation |
| {{< ref "/programming-languages/typescript/20-abstract-classes" >}} | Abstract classes and static members |
| {{< ref "/programming-languages/typescript/21-inheritance" >}} | Inheritance and method overriding |
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro