Falytom logoFALYTOM
Games
ToolsUI ComponentsServicesProductsAbout
LoginSign Up
menu
  1. Home
  2. /Web Development
  3. /Typescript
  4. /Classes Constructors
Your Progress0%
0 of 30 completed

TypeScript Topics

Getting Started

  • What is TypeScript?
  • TypeScript vs JavaScript
  • Setting Up TypeScript
  • Your First TypeScript Program

Basic Types

  • Primitive Types
  • Arrays and Tuples
  • Objects and Type Aliases
  • Enums
  • Any, Unknown, and Never

Functions & Interfaces

  • Function Types and Signatures
  • Interfaces
  • Optional and Default Parameters
  • Function Overloading

Advanced Types

  • Union and Intersection Types
  • Type Guards and Narrowing
  • Literal Types
  • Type Assertions and Casting

Generics

  • Introduction to Generics
  • Generic Constraints
  • Utility Types

Classes & OOP

  • Classes and Constructors
  • Access Modifiers
  • Inheritance and Polymorphism
  • Abstract Classes and Interfaces

Practical TypeScript

  • Working with Modules
  • TypeScript with React
  • Async/Await and Promises
  • Working with APIs and JSON
  • Type Declarations and DefinitelyTyped
  • TypeScript Best Practices

Classes and Constructors

Creating typed classes with constructors and properties

Classes are the foundation of object-oriented programming in TypeScript. They let you create blueprints for objects with typed properties, methods, and constructors. TypeScript enhances JavaScript classes with type safety, access modifiers, and powerful OOP features. Let's master classes and constructors!

Basic Class Syntax

A class is a blueprint for creating objects. It defines properties (data) and methods (behavior) that objects will have.

basic-class.ts
// Basic class definition
class User {
  // Properties with types
  name: string;
  email: string;
  age: number;
  
  // Constructor - called when creating new instances
  constructor(name: string, email: string, age: number) {
    this.name = name;
    this.email = email;
    this.age = age;
  }
  
  // Method
  displayInfo(): void {
    console.log(`Name: ${this.name}`);
    console.log(`Email: ${this.email}`);
    console.log(`Age: ${this.age}`);
  }
  
  // Method with return value
  getGreeting(): string {
    return `Hello, my name is ${this.name}`;
  }
}

// Create instances using 'new' keyword
const user1 = new User("Yusuf Ibrahim", "yusuf@example.com", 25);
const user2 = new User("Amina Hassan", "amina@example.com", 30);

// Access properties
console.log(user1.name);   // "Yusuf Ibrahim"
console.log(user1.email);  // "yusuf@example.com"

// Call methods
user1.displayInfo();
// Name: Yusuf Ibrahim
// Email: yusuf@example.com
// Age: 25

console.log(user2.getGreeting());  // "Hello, my name is Amina Hassan"

// Modify properties
user1.age = 26;
console.log(user1.age);  // 26

💡 The 'this' Keyword

Inside a class, this refers to the current instance. Use this.propertyName to access properties and this.methodName() to call methods.

Constructors

The constructor is a special method called when creating a new instance. It initializes the object's properties.

Basic Constructor

constructors-basic.ts
class Product {
  id: number;
  name: string;
  price: number;
  
  constructor(id: number, name: string, price: number) {
    this.id = id;
    this.name = name;
    this.price = price;
    
    console.log(`Product created: ${name}`);
  }
  
  getFormattedPrice(): string {
    return `₦${this.price.toLocaleString()}`;
  }
}

const laptop = new Product(1, "Laptop", 450000);
// Logs: "Product created: Laptop"

console.log(laptop.getFormattedPrice());  // "₦450,000"

const mouse = new Product(2, "Mouse", 5000);
// Logs: "Product created: Mouse"

Constructor with Default Values

constructor-defaults.ts
class Account {
  username: string;
  email: string;
  role: string;
  active: boolean;
  
  // Constructor with default parameters
  constructor(
    username: string,
    email: string,
    role: string = "user",      // Default value
    active: boolean = true       // Default value
  ) {
    this.username = username;
    this.email = email;
    this.role = role;
    this.active = active;
  }
  
  displayStatus(): void {
    const status = this.active ? "Active" : "Inactive";
    console.log(`${this.username} (${this.role}): ${status}`);
  }
}

// Using defaults
const user1 = new Account("yusuf", "yusuf@example.com");
user1.displayStatus();  // "yusuf (user): Active"

// Overriding defaults
const admin = new Account("amina", "amina@example.com", "admin", true);
admin.displayStatus();  // "amina (admin): Active"

const inactive = new Account("chidi", "chidi@example.com", "editor", false);
inactive.displayStatus();  // "chidi (editor): Inactive"

Constructor Validation

constructor-validation.ts
class BankAccount {
  accountNumber: string;
  balance: number;
  
  constructor(accountNumber: string, initialBalance: number) {
    // Validate account number
    if (accountNumber.length !== 10) {
      throw new Error("Account number must be 10 digits");
    }
    
    // Validate initial balance
    if (initialBalance < 0) {
      throw new Error("Initial balance cannot be negative");
    }
    
    this.accountNumber = accountNumber;
    this.balance = initialBalance;
  }
  
  deposit(amount: number): void {
    if (amount <= 0) {
      throw new Error("Deposit amount must be positive");
    }
    this.balance += amount;
    console.log(`Deposited ₦${amount}. New balance: ₦${this.balance}`);
  }
  
  getBalance(): number {
    return this.balance;
  }
}

// Valid account
const account1 = new BankAccount("0123456789", 10000);
account1.deposit(5000);  // "Deposited ₦5000. New balance: ₦15000"

// Invalid account number
try {
  const account2 = new BankAccount("123", 5000);
} catch (error) {
  console.error((error as Error).message);  // "Account number must be 10 digits"
}

// Invalid initial balance
try {
  const account3 = new BankAccount("0123456789", -100);
} catch (error) {
  console.error((error as Error).message);  // "Initial balance cannot be negative"
}

Parameter Properties

TypeScript provides a shorthand for declaring and initializing properties directly in the constructor parameters.

parameter-properties.ts
// Traditional approach
class UserTraditional {
  name: string;
  email: string;
  age: number;
  
  constructor(name: string, email: string, age: number) {
    this.name = name;
    this.email = email;
    this.age = age;
  }
}

// Parameter properties shorthand
class UserShorthand {
  // Adding access modifier creates property automatically
  constructor(
    public name: string,
    public email: string,
    public age: number
  ) {
    // No need to assign - it's done automatically!
  }
}

const user1 = new UserShorthand("Yusuf", "yusuf@example.com", 25);
console.log(user1.name);   // "Yusuf"
console.log(user1.email);  // "yusuf@example.com"
console.log(user1.age);    // 25

// Can mix parameter properties with regular ones
class Product {
  // Parameter properties
  constructor(
    public id: number,
    public name: string,
    public price: number
  ) {
    // Can add initialization logic here
    console.log(`Product ${this.id} created`);
  }
  
  // Regular methods work normally
  getFormattedPrice(): string {
    return `₦${this.price.toLocaleString()}`;
  }
}

const laptop = new Product(1, "Laptop", 450000);
// Logs: "Product 1 created"
console.log(laptop.getFormattedPrice());  // "₦450,000"

// Can combine with default values
class Settings {
  constructor(
    public theme: string = "light",
    public language: string = "en",
    public notifications: boolean = true
  ) {}
}

const defaultSettings = new Settings();
console.log(defaultSettings.theme);  // "light"

const customSettings = new Settings("dark", "fr", false);
console.log(customSettings.theme);  // "dark"

Class Properties

Property Initialization

property-initialization.ts
class User {
  // Initialize at declaration
  role: string = "user";
  active: boolean = true;
  loginCount: number = 0;
  
  // Uninitialized (must be set in constructor or marked with !)
  name: string;
  email: string;
  
  // Readonly property (can only be set in constructor)
  readonly id: number;
  
  constructor(name: string, email: string) {
    this.name = name;
    this.email = email;
    this.id = Date.now();  // Set once in constructor
  }
  
  incrementLoginCount(): void {
    this.loginCount++;
    console.log(`Login count: ${this.loginCount}`);
  }
}

const user = new User("Yusuf", "yusuf@example.com");

console.log(user.role);        // "user" (default)
console.log(user.active);      // true (default)
console.log(user.loginCount);  // 0 (default)
console.log(user.id);          // Timestamp

user.incrementLoginCount();    // "Login count: 1"
user.role = "admin";           // ✅ Can modify

// user.id = 123;              // ❌ Error: Cannot assign to 'id' because it is readonly

// Optional properties
class Profile {
  name: string;
  bio?: string;        // Optional property
  website?: string;    // Optional property
  
  constructor(name: string) {
    this.name = name;
  }
  
  displayBio(): void {
    if (this.bio) {
      console.log(this.bio);
    } else {
      console.log("No bio available");
    }
  }
}

const profile = new Profile("Yusuf Ibrahim");
profile.displayBio();  // "No bio available"

profile.bio = "Full-stack developer";
profile.displayBio();  // "Full-stack developer"

Computed Properties

computed-properties.ts
class Rectangle {
  constructor(
    public width: number,
    public height: number
  ) {}
  
  // Computed property using getter
  get area(): number {
    return this.width * this.height;
  }
  
  get perimeter(): number {
    return 2 * (this.width + this.height);
  }
  
  // Setter for validation
  set width(value: number) {
    if (value <= 0) {
      throw new Error("Width must be positive");
    }
    this.width = value;
  }
}

const rect = new Rectangle(10, 20);

console.log(rect.area);       // 200 (computed)
console.log(rect.perimeter);  // 60 (computed)

// Access like properties (no parentheses)
rect.width = 15;
console.log(rect.area);  // 300 (automatically recalculated)

class Person {
  constructor(
    public firstName: string,
    public lastName: string
  ) {}
  
  // Getter - computed from other properties
  get fullName(): string {
    return `${this.firstName} ${this.lastName}`;
  }
  
  // Setter - updates multiple properties
  set fullName(value: string) {
    const parts = value.split(" ");
    this.firstName = parts[0];
    this.lastName = parts[parts.length - 1];
  }
}

const person = new Person("Yusuf", "Ibrahim");
console.log(person.fullName);  // "Yusuf Ibrahim"

person.fullName = "Amina Hassan";
console.log(person.firstName);  // "Amina"
console.log(person.lastName);   // "Hassan"

Class Methods

class-methods.ts
class Calculator {
  // Instance property
  result: number = 0;
  
  // Instance methods
  add(value: number): this {
    this.result += value;
    return this;  // Enable method chaining
  }
  
  subtract(value: number): this {
    this.result -= value;
    return this;
  }
  
  multiply(value: number): this {
    this.result *= value;
    return this;
  }
  
  divide(value: number): this {
    if (value === 0) {
      throw new Error("Cannot divide by zero");
    }
    this.result /= value;
    return this;
  }
  
  getResult(): number {
    return this.result;
  }
  
  reset(): void {
    this.result = 0;
  }
}

const calc = new Calculator();

// Method chaining
const result = calc
  .add(10)
  .multiply(5)
  .subtract(20)
  .divide(3)
  .getResult();

console.log(result);  // 10

calc.reset();
console.log(calc.getResult());  // 0

// Methods with different signatures
class StringUtils {
  static capitalize(str: string): string {
    return str.charAt(0).toUpperCase() + str.slice(1);
  }
  
  static reverse(str: string): string {
    return str.split("").reverse().join("");
  }
  
  static truncate(str: string, maxLength: number): string {
    if (str.length <= maxLength) {
      return str;
    }
    return str.slice(0, maxLength - 3) + "...";
  }
}

console.log(StringUtils.capitalize("hello"));           // "Hello"
console.log(StringUtils.reverse("typescript"));         // "tpircsepyt"
console.log(StringUtils.truncate("Long text here", 8)); // "Long ..."

// Instance vs Static methods
class Counter {
  private count: number = 0;
  
  // Instance method - works with instance data
  increment(): void {
    this.count++;
  }
  
  getCount(): number {
    return this.count;
  }
  
  // Static method - doesn't use instance data
  static createWithInitialValue(value: number): Counter {
    const counter = new Counter();
    counter.count = value;
    return counter;
  }
}

const counter1 = new Counter();
counter1.increment();
counter1.increment();
console.log(counter1.getCount());  // 2

const counter2 = Counter.createWithInitialValue(10);
console.log(counter2.getCount());  // 10

Practical Examples

Example 1: Shopping Cart

shopping-cart.ts
class CartItem {
  constructor(
    public productId: number,
    public name: string,
    public price: number,
    public quantity: number = 1
  ) {}
  
  getSubtotal(): number {
    return this.price * this.quantity;
  }
  
  getFormattedSubtotal(): string {
    return `₦${this.getSubtotal().toLocaleString()}`;
  }
}

class ShoppingCart {
  private items: CartItem[] = [];
  
  addItem(productId: number, name: string, price: number, quantity: number = 1): void {
    // Check if item already exists
    const existingItem = this.items.find(item => item.productId === productId);
    
    if (existingItem) {
      existingItem.quantity += quantity;
    } else {
      this.items.push(new CartItem(productId, name, price, quantity));
    }
    
    console.log(`Added ${quantity}x ${name} to cart`);
  }
  
  removeItem(productId: number): void {
    const index = this.items.findIndex(item => item.productId === productId);
    
    if (index !== -1) {
      const item = this.items[index];
      console.log(`Removed ${item.name} from cart`);
      this.items.splice(index, 1);
    }
  }
  
  updateQuantity(productId: number, quantity: number): void {
    const item = this.items.find(item => item.productId === productId);
    
    if (item) {
      item.quantity = quantity;
      console.log(`Updated ${item.name} quantity to ${quantity}`);
    }
  }
  
  getTotal(): number {
    return this.items.reduce((total, item) => total + item.getSubtotal(), 0);
  }
  
  getFormattedTotal(): string {
    return `₦${this.getTotal().toLocaleString()}`;
  }
  
  getItemCount(): number {
    return this.items.reduce((count, item) => count + item.quantity, 0);
  }
  
  displayCart(): void {
    console.log("\n=== Shopping Cart ===");
    
    if (this.items.length === 0) {
      console.log("Cart is empty");
      return;
    }
    
    this.items.forEach(item => {
      console.log(`${item.name} x${item.quantity} - ${item.getFormattedSubtotal()}`);
    });
    
    console.log(`\nTotal Items: ${this.getItemCount()}`);
    console.log(`Total: ${this.getFormattedTotal()}`);
    console.log("===================\n");
  }
  
  clear(): void {
    this.items = [];
    console.log("Cart cleared");
  }
}

// Usage
const cart = new ShoppingCart();

cart.addItem(1, "Laptop", 450000, 1);
cart.addItem(2, "Mouse", 5000, 2);
cart.addItem(3, "Keyboard", 15000, 1);

cart.displayCart();
// === Shopping Cart ===
// Laptop x1 - ₦450,000
// Mouse x2 - ₦10,000
// Keyboard x1 - ₦15,000
//
// Total Items: 4
// Total: ₦475,000
// ===================

cart.updateQuantity(2, 3);
cart.displayCart();

cart.removeItem(3);
cart.displayCart();

Example 2: Task Manager

task-manager.ts
type TaskStatus = "pending" | "in-progress" | "completed";
type TaskPriority = "low" | "medium" | "high";

class Task {
  readonly id: number;
  title: string;
  description: string;
  status: TaskStatus;
  priority: TaskPriority;
  readonly createdAt: Date;
  updatedAt: Date;
  
  constructor(
    title: string,
    description: string,
    priority: TaskPriority = "medium"
  ) {
    this.id = Date.now();
    this.title = title;
    this.description = description;
    this.status = "pending";
    this.priority = priority;
    this.createdAt = new Date();
    this.updatedAt = new Date();
  }
  
  start(): void {
    if (this.status !== "pending") {
      console.log("Task already started or completed");
      return;
    }
    
    this.status = "in-progress";
    this.updatedAt = new Date();
    console.log(`Started task: ${this.title}`);
  }
  
  complete(): void {
    this.status = "completed";
    this.updatedAt = new Date();
    console.log(`Completed task: ${this.title}`);
  }
  
  updatePriority(priority: TaskPriority): void {
    this.priority = priority;
    this.updatedAt = new Date();
    console.log(`Updated priority to ${priority}`);
  }
  
  displayInfo(): void {
    console.log(`\n[${this.status.toUpperCase()}] ${this.title}`);
    console.log(`Priority: ${this.priority}`);
    console.log(`Description: ${this.description}`);
    console.log(`Created: ${this.createdAt.toLocaleString()}`);
  }
}

class TaskManager {
  private tasks: Task[] = [];
  
  addTask(title: string, description: string, priority?: TaskPriority): Task {
    const task = new Task(title, description, priority);
    this.tasks.push(task);
    console.log(`Added task: ${title}`);
    return task;
  }
  
  getTaskById(id: number): Task | undefined {
    return this.tasks.find(task => task.id === id);
  }
  
  getTasksByStatus(status: TaskStatus): Task[] {
    return this.tasks.filter(task => task.status === status);
  }
  
  getTasksByPriority(priority: TaskPriority): Task[] {
    return this.tasks.filter(task => task.priority === priority);
  }
  
  displayAllTasks(): void {
    console.log(`\n=== All Tasks (${this.tasks.length}) ===`);
    
    if (this.tasks.length === 0) {
      console.log("No tasks");
      return;
    }
    
    this.tasks.forEach(task => task.displayInfo());
  }
  
  displaySummary(): void {
    const pending = this.getTasksByStatus("pending").length;
    const inProgress = this.getTasksByStatus("in-progress").length;
    const completed = this.getTasksByStatus("completed").length;
    
    console.log(`\n=== Task Summary ===`);
    console.log(`Pending: ${pending}`);
    console.log(`In Progress: ${inProgress}`);
    console.log(`Completed: ${completed}`);
    console.log(`Total: ${this.tasks.length}`);
  }
}

// Usage
const manager = new TaskManager();

const task1 = manager.addTask(
  "Build TypeScript tutorial",
  "Create comprehensive TypeScript classes tutorial",
  "high"
);

const task2 = manager.addTask(
  "Review code",
  "Review pull requests",
  "medium"
);

const task3 = manager.addTask(
  "Update documentation",
  "Update API documentation",
  "low"
);

task1.start();
task1.complete();

task2.start();

manager.displaySummary();
// === Task Summary ===
// Pending: 1
// In Progress: 1
// Completed: 1
// Total: 3

manager.displayAllTasks();

Test Your Knowledge

Type Safety Check

Which class definition is correct?

// Option A
class User {
  name: string;
  email: string;
  
  constructor(name: string, email: string) {
    this.name = name;
    this.email = email;
  }
}

// Option B
class User {
  constructor(name: string, email: string) {
    this.name = name;
    this.email = email;
  }
}

// Option C
class User {
  name: string;
  email: string;
}

const user = new User();
user.name = "Yusuf";
user.email = "yusuf@example.com";

// Option D
class User {
  constructor(public name: string, public email: string) {
    // Properties automatically created
  }
}

Common TypeScript Error

Code with Error
// Using properties before declaration
class User {
  constructor(name: string, email: string) {
    this.name = name;      // ❌ Error: Property 'name' does not exist
    this.email = email;    // ❌ Error: Property 'email' does not exist
  }
  
  displayInfo() {
    console.log(`${this.name}: ${this.email}`);
  }
}

❌ Property 'name' does not exist on type 'User'. Property 'email' does not exist on type 'User'.

What's Wrong?

TypeScript requires all properties to be declared in the class body before they can be used, even in the constructor. This ensures type safety.

Corrected Code
// Fix 1: Declare properties explicitly
class User {
  name: string;
  email: string;
  
  constructor(name: string, email: string) {
    this.name = name;      // ✅ Works - property declared
    this.email = email;    // ✅ Works - property declared
  }
  
  displayInfo(): void {
    console.log(`${this.name}: ${this.email}`);
  }
}

const user1 = new User("Yusuf", "yusuf@example.com");
user1.displayInfo();  // "Yusuf: yusuf@example.com"

// Fix 2: Use parameter properties (shorthand)
class UserShorthand {
  constructor(
    public name: string,
    public email: string
  ) {
    // Properties automatically created and initialized
  }
  
  displayInfo(): void {
    console.log(`${this.name}: ${this.email}`);
  }
}

const user2 = new UserShorthand("Amina", "amina@example.com");
user2.displayInfo();  // "Amina: amina@example.com"

Solution: Declare properties in the class body or use parameter properties

Best Practices

  1. Always declare property types - type safety is key
  2. Use parameter properties for simple classes - reduces boilerplate
  3. Initialize properties - in declaration or constructor
  4. Validate in constructor - catch errors early
  5. Use readonly for immutable properties - prevent accidental changes
  6. Return 'this' for method chaining - better developer experience
  7. Keep classes focused - single responsibility principle
  8. Use getters/setters for computed or validated properties
best-practices-example.ts
// ✅ Good: Clear, typed, validated
class Product {
  readonly id: number;  // Immutable after creation
  
  constructor(
    public name: string,
    public price: number,
    public category: string
  ) {
    // Validation
    if (price < 0) {
      throw new Error("Price cannot be negative");
    }
    
    if (!name.trim()) {
      throw new Error("Name is required");
    }
    
    this.id = Date.now();
  }
  
  // Method chaining
  updatePrice(newPrice: number): this {
    if (newPrice < 0) {
      throw new Error("Price cannot be negative");
    }
    this.price = newPrice;
    return this;
  }
  
  // Computed property
  get formattedPrice(): string {
    return `₦${this.price.toLocaleString()}`;
  }
}

// ✅ Good: Focused responsibility
class Logger {
  private logs: string[] = [];
  
  log(message: string): void {
    const timestamp = new Date().toISOString();
    this.logs.push(`[${timestamp}] ${message}`);
  }
  
  getLogs(): readonly string[] {
    return [...this.logs];  // Return copy
  }
}

// ❌ Avoid: Uninitialized properties
class Bad {
  name: string;  // Not initialized!
  // Will be undefined unless set
}

// ❌ Avoid: No validation
class AlsoBad {
  constructor(public price: number) {
    // Should validate price > 0
  }
}

Key Takeaways

  • Classes are blueprints for creating typed objects
  • Constructors initialize new instances with the new keyword
  • Properties must be declared with types before use
  • Parameter properties (with access modifiers) create properties automatically
  • Use this to access instance properties and methods
  • readonly properties can only be set in the constructor
  • Getters and setters enable computed and validated properties
  • Validate constructor parameters to catch errors early
  • Return this from methods to enable method chaining
  • Classes provide structure for object-oriented programming

What's Next?

Excellent work! You've mastered classes and constructors in TypeScript. Next, we'll explore Access Modifiers—how to control visibility of properties and methods using public, private, and protected. This gives you fine- grained control over encapsulation and data hiding in your classes!

Get ready to master encapsulation and data protection!

Mastering TypeScript classes! Check this out!

Previous
Utility Types
Next
Access Modifiers

Never Miss a New TypeScript Tutorial

Join 1,000+ developers learning TypeScript step-by-step. Get new tutorials, type safety tips, and exclusive resources delivered to your inbox - completely FREE.

You might also like

UI Components

Ready-made components for your projects

Developer Tools

Boost productivity with handy generators

HTML Game

Learn HTML concepts through interactive gameplay

Join Our Web Development Training Program

Master frontend & backend development with hands-on projects.

TypeScript Tutorials

0 of 30 completed

Your Progress0%

Topics

Getting Started

  • What is TypeScript?
  • TypeScript vs JavaScript
  • Setting Up TypeScript
  • Your First TypeScript Program

Basic Types

  • Primitive Types
  • Arrays and Tuples
  • Objects and Type Aliases
  • Enums
  • Any, Unknown, and Never

Functions & Interfaces

  • Function Types and Signatures
  • Interfaces
  • Optional and Default Parameters
  • Function Overloading

Advanced Types

  • Union and Intersection Types
  • Type Guards and Narrowing
  • Literal Types
  • Type Assertions and Casting

Generics

  • Introduction to Generics
  • Generic Constraints
  • Utility Types

Classes & OOP

  • Classes and Constructors
  • Access Modifiers
  • Inheritance and Polymorphism
  • Abstract Classes and Interfaces

Practical TypeScript

  • Working with Modules
  • TypeScript with React
  • Async/Await and Promises
  • Working with APIs and JSON
  • Type Declarations and DefinitelyTyped
  • TypeScript Best Practices
Falytom logoFALYTOM

Learn technologies like HTML, CSS, JavaScript, React, NodeJS and more through interactive games, tutorials, components and free tools. Transform your businesses with AI chatbots, SEO-optimized websites, and professional development services.

© 2025 Tomilola Group
  • Facebook logo
  • Twitter logo
  • Instagram logo
  • LinkedIn logo
  • GitHub logo
  • YouTube logo