+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Part 27 of 355

๐Ÿ”ง Class Properties and Methods: Building Class Members

Master TypeScript class properties and methods with advanced patterns, parameter properties, and real-world examples ๐Ÿš€

๐Ÿš€Intermediate
30 min read

Prerequisites

  • Basic understanding of TypeScript classes ๐Ÿ—๏ธ
  • Familiarity with constructors and objects ๐Ÿ“š
  • Knowledge of access modifiers ๐Ÿ”’

What you'll learn

  • Master different property initialization patterns ๐ŸŽฏ
  • Create flexible methods with various signatures ๐Ÿ› ๏ธ
  • Use parameter properties for cleaner code โœจ
  • Implement advanced class member patterns ๐Ÿš€

๐ŸŽฏ Introduction

Welcome to the deep dive into TypeScript class properties and methods! ๐ŸŽ‰ Think of this as your masterclass in crafting the perfect class members - the heart and soul of your objects.

Properties are like the DNA ๐Ÿงฌ of your objects (what they are), while methods are like their superpowers ๐Ÿ’ช (what they can do). Together, they create the complete picture of your classโ€™s capabilities.

By the end of this tutorial, youโ€™ll be a class member wizard, crafting elegant, efficient, and maintainable code that your future self will thank you for! โœจ

Letโ€™s unlock the full potential of TypeScript class members! ๐Ÿš€

๐Ÿ“š Understanding Class Members

๐Ÿค” What are Class Properties and Methods?

Class members are the building blocks of your classes:

  • Properties ๐Ÿ“Š: Store data and state
  • Methods โšก: Define behavior and actions
  • Accessors ๐Ÿ”: Control how properties are accessed
  • Static Members ๐Ÿ›๏ธ: Belong to the class itself

Think of a smartphone ๐Ÿ“ฑ:

  • Properties: battery level, screen size, storage capacity
  • Methods: makeCall(), sendText(), takePhoto()
  • Accessors: getBatteryLevel(), setVolume()
  • Static: getManufacturer(), getModel()

๐Ÿ’ก Why Master Class Members?

Hereโ€™s why understanding class members is crucial:

  1. Encapsulation ๐Ÿ”’: Control access to your data
  2. Flexibility ๐Ÿคธ: Adapt to changing requirements
  3. Maintainability ๐Ÿ› ๏ธ: Easy to update and debug
  4. Performance โšก: Optimize memory usage and execution
  5. Type Safety ๐Ÿ›ก๏ธ: Catch errors at compile time

๐Ÿ”ง Property Initialization Patterns

๐Ÿ“ Basic Property Declaration

Letโ€™s explore different ways to declare and initialize properties:

// ๐ŸŽฎ Game Character class with various property patterns
class GameCharacter {
  // ๐ŸŽฏ Explicit type with initial value
  name: string = "Unknown Hero";
  
  // ๐Ÿ”ข Number property with default
  level: number = 1;
  
  // ๐ŸŽญ Optional property
  title?: string;
  
  // ๐Ÿ”’ Readonly property
  readonly id: string = crypto.randomUUID();
  
  // ๐Ÿ“… Computed property
  createdAt: Date = new Date();
  
  // ๐ŸŽจ Union type property
  status: "active" | "inactive" | "banned" = "active";
  
  // ๐Ÿ“Š Array property
  inventory: string[] = [];
  
  // ๐Ÿ—บ๏ธ Object property
  stats: { strength: number; agility: number; intelligence: number } = {
    strength: 10,
    agility: 10,
    intelligence: 10
  };
  
  constructor(name: string, title?: string) {
    this.name = name;
    if (title) {
      this.title = title;
    }
    
    console.log(`โš”๏ธ ${this.name} has entered the game!`);
  }
}

// ๐Ÿš€ Create characters
const warrior = new GameCharacter("Thorin", "The Brave");
const mage = new GameCharacter("Gandalf", "The Wise");
const archer = new GameCharacter("Legolas");

console.log(warrior.name);     // "Thorin"
console.log(warrior.title);    // "The Brave"
console.log(archer.title);     // undefined

โšก Parameter Properties Shorthand

TypeScriptโ€™s parameter properties let you declare and initialize properties in one go:

// ๐Ÿช Product class using parameter properties
class Product {
  // ๐ŸŽฏ Traditional way (verbose)
  // private _id: string;
  // public name: string;
  // protected price: number;
  // readonly category: string;
  // 
  // constructor(id: string, name: string, price: number, category: string) {
  //   this._id = id;
  //   this.name = name;
  //   this.price = price;
  //   this.category = category;
  // }
  
  // โœจ Parameter properties way (concise!)
  constructor(
    private _id: string,              // ๐Ÿ”’ Private property
    public name: string,              // โœ… Public property
    protected price: number,          // ๐Ÿ‘ฅ Protected property
    readonly category: string,        // ๐Ÿ“– Readonly property
    public description: string = "",  // ๐ŸŽฏ Default value
    public inStock: boolean = true    // ๐Ÿ“ฆ Stock status
  ) {
    console.log(`๐Ÿ“ฆ Product "${name}" created in ${category} category`);
  }
  
  // ๐Ÿ” Getter for private property
  get id(): string {
    return this._id;
  }
  
  // ๐Ÿ’ฐ Price getter (formatted)
  get formattedPrice(): string {
    return `$${this.price.toFixed(2)}`;
  }
  
  // ๐Ÿ“Š Product info
  getInfo(): string {
    const stock = this.inStock ? "โœ… In Stock" : "โŒ Out of Stock";
    return `๐Ÿ›๏ธ ${this.name} - ${this.formattedPrice} (${this.category}) ${stock}`;
  }
  
  // ๐Ÿ”„ Update stock status
  updateStock(inStock: boolean): void {
    this.inStock = inStock;
    const status = inStock ? "restocked" : "sold out";
    console.log(`๐Ÿ“ฆ ${this.name} is now ${status}`);
  }
}

// ๐Ÿš€ Create products
const laptop = new Product(
  "P001", 
  "Gaming Laptop", 
  2499.99, 
  "Electronics",
  "High-performance gaming laptop with RTX graphics"
);

const book = new Product("P002", "TypeScript Guide", 39.99, "Books");

console.log(laptop.getInfo());
console.log(book.formattedPrice);
laptop.updateStock(false);

๐Ÿ’ก Method Patterns and Signatures

๐ŸŽฏ Method Overloading

Create flexible methods with multiple signatures:

// ๐Ÿ“Š Calculator class with method overloading
class Calculator {
  // ๐Ÿ”ข Method overloads - different signatures
  add(a: number, b: number): number;
  add(a: string, b: string): string;
  add(a: number[], b: number[]): number[];
  add(a: any, b: any): any {
    // ๐ŸŽฏ Implementation handles all cases
    if (typeof a === "number" && typeof b === "number") {
      return a + b;
    }
    
    if (typeof a === "string" && typeof b === "string") {
      return a + b;
    }
    
    if (Array.isArray(a) && Array.isArray(b)) {
      return [...a, ...b];
    }
    
    throw new Error("โŒ Unsupported types for addition");
  }
  
  // ๐ŸŽจ Flexible subtract with optional parameters
  subtract(a: number, b: number): number;
  subtract(a: number, b: number, c: number): number;
  subtract(a: number, b?: number, c?: number): number {
    let result = a;
    if (b !== undefined) result -= b;
    if (c !== undefined) result -= c;
    return result;
  }
  
  // ๐Ÿ“Š Statistics method with rest parameters
  average(...numbers: number[]): number {
    if (numbers.length === 0) return 0;
    const sum = numbers.reduce((acc, num) => acc + num, 0);
    return sum / numbers.length;
  }
  
  // ๐ŸŽฏ Generic method
  process<T>(items: T[], processor: (item: T) => T): T[] {
    return items.map(processor);
  }
}

// ๐Ÿš€ Test the calculator
const calc = new Calculator();

console.log(calc.add(5, 3));                    // 8 (numbers)
console.log(calc.add("Hello", " World"));       // "Hello World" (strings)
console.log(calc.add([1, 2], [3, 4]));         // [1, 2, 3, 4] (arrays)

console.log(calc.subtract(10, 5));              // 5
console.log(calc.subtract(10, 5, 2));           // 3

console.log(calc.average(1, 2, 3, 4, 5));       // 3

// ๐ŸŽจ Generic method usage
const doubled = calc.process([1, 2, 3], x => x * 2);
console.log(doubled); // [2, 4, 6]

๐Ÿ”„ Fluent Interface Pattern

Chain methods for elegant API design:

// ๐ŸŽจ CSS Style Builder with fluent interface
class StyleBuilder {
  private styles: Record<string, string> = {};
  
  // ๐ŸŽฏ Color methods
  color(color: string): this {
    this.styles.color = color;
    return this;
  }
  
  backgroundColor(color: string): this {
    this.styles.backgroundColor = color;
    return this;
  }
  
  // ๐Ÿ“ Size methods
  width(width: string | number): this {
    this.styles.width = typeof width === "number" ? `${width}px` : width;
    return this;
  }
  
  height(height: string | number): this {
    this.styles.height = typeof height === "number" ? `${height}px` : height;
    return this;
  }
  
  // ๐Ÿ“ Position methods
  position(position: "static" | "relative" | "absolute" | "fixed"): this {
    this.styles.position = position;
    return this;
  }
  
  top(top: string | number): this {
    this.styles.top = typeof top === "number" ? `${top}px` : top;
    return this;
  }
  
  left(left: string | number): this {
    this.styles.left = typeof left === "number" ? `${left}px` : left;
    return this;
  }
  
  // ๐ŸŽจ Typography methods
  fontSize(size: string | number): this {
    this.styles.fontSize = typeof size === "number" ? `${size}px` : size;
    return this;
  }
  
  fontWeight(weight: "normal" | "bold" | "bolder" | "lighter" | number): this {
    this.styles.fontWeight = weight.toString();
    return this;
  }
  
  textAlign(align: "left" | "center" | "right" | "justify"): this {
    this.styles.textAlign = align;
    return this;
  }
  
  // ๐Ÿ“ฆ Layout methods
  display(display: "block" | "inline" | "flex" | "grid" | "none"): this {
    this.styles.display = display;
    return this;
  }
  
  flexDirection(direction: "row" | "column" | "row-reverse" | "column-reverse"): this {
    this.styles.flexDirection = direction;
    return this;
  }
  
  justifyContent(justify: "flex-start" | "flex-end" | "center" | "space-between" | "space-around"): this {
    this.styles.justifyContent = justify;
    return this;
  }
  
  // ๐ŸŽฏ Utility methods
  reset(): this {
    this.styles = {};
    return this;
  }
  
  build(): Record<string, string> {
    return { ...this.styles };
  }
  
  buildCSS(): string {
    return Object.entries(this.styles)
      .map(([key, value]) => `${key}: ${value};`)
      .join(' ');
  }
  
  // ๐ŸŽจ Preset styles
  card(): this {
    return this
      .backgroundColor("#ffffff")
      .color("#333333")
      .width(300)
      .height(200)
      .position("relative")
      .display("flex")
      .flexDirection("column")
      .justifyContent("center");
  }
  
  button(): this {
    return this
      .backgroundColor("#007bff")
      .color("#ffffff")
      .width(120)
      .height(40)
      .fontSize(14)
      .fontWeight("bold")
      .textAlign("center")
      .display("flex")
      .justifyContent("center");
  }
}

// ๐Ÿš€ Build styles with fluent interface
const cardStyle = new StyleBuilder()
  .card()
  .backgroundColor("#f8f9fa")
  .color("#212529")
  .build();

const buttonStyle = new StyleBuilder()
  .button()
  .backgroundColor("#28a745")
  .width(150)
  .buildCSS();

console.log("๐ŸŽจ Card styles:", cardStyle);
console.log("๐ŸŽจ Button CSS:", buttonStyle);

// ๐Ÿ”„ Chain multiple styles
const headerStyle = new StyleBuilder()
  .fontSize(24)
  .fontWeight("bold")
  .color("#333")
  .textAlign("center")
  .display("block")
  .buildCSS();

console.log("๐Ÿ“ฐ Header CSS:", headerStyle);

๐Ÿ” Getters and Setters (Accessors)

๐ŸŽฏ Property Accessors

Control how properties are accessed and modified:

// ๐ŸŒก๏ธ Temperature class with smart accessors
class Temperature {
  private _celsius: number = 0;
  
  constructor(celsius: number = 0) {
    this.celsius = celsius; // Use setter for validation
  }
  
  // ๐ŸŒก๏ธ Celsius getter
  get celsius(): number {
    return this._celsius;
  }
  
  // ๐ŸŒก๏ธ Celsius setter with validation
  set celsius(value: number) {
    if (value < -273.15) {
      throw new Error("โ„๏ธ Temperature cannot be below absolute zero!");
    }
    this._celsius = value;
    console.log(`๐ŸŒก๏ธ Temperature set to ${value}ยฐC`);
  }
  
  // ๐Ÿ‡บ๐Ÿ‡ธ Fahrenheit getter (computed property)
  get fahrenheit(): number {
    return (this._celsius * 9/5) + 32;
  }
  
  // ๐Ÿ‡บ๐Ÿ‡ธ Fahrenheit setter
  set fahrenheit(value: number) {
    this.celsius = (value - 32) * 5/9;
  }
  
  // ๐Ÿ”ฌ Kelvin getter
  get kelvin(): number {
    return this._celsius + 273.15;
  }
  
  // ๐Ÿ”ฌ Kelvin setter
  set kelvin(value: number) {
    this.celsius = value - 273.15;
  }
  
  // ๐ŸŽฏ Status getter
  get status(): string {
    if (this._celsius <= 0) return "๐ŸงŠ Freezing";
    if (this._celsius < 20) return "โ„๏ธ Cold";
    if (this._celsius < 30) return "๐ŸŒค๏ธ Comfortable";
    if (this._celsius < 40) return "๐ŸŒž Hot";
    return "๐Ÿ”ฅ Extremely Hot";
  }
  
  // ๐Ÿ“Š Temperature info
  getInfo(): string {
    return `${this.status} - ${this._celsius}ยฐC / ${this.fahrenheit.toFixed(1)}ยฐF / ${this.kelvin.toFixed(1)}K`;
  }
}

// ๐Ÿš€ Test temperature conversions
const temp = new Temperature(25);
console.log(temp.getInfo());    // ๐ŸŒค๏ธ Comfortable - 25ยฐC / 77.0ยฐF / 298.2K

temp.fahrenheit = 100;          // Set via Fahrenheit
console.log(temp.getInfo());    // ๐ŸŒž Hot - 37.8ยฐC / 100.0ยฐF / 310.9K

temp.kelvin = 300;              // Set via Kelvin
console.log(temp.getInfo());    // ๐ŸŒค๏ธ Comfortable - 26.9ยฐC / 80.3ยฐF / 300.0K

// temp.celsius = -300;         // โŒ Would throw error!

๐Ÿ›ก๏ธ Validation and Computed Properties

// ๐Ÿ‘ค User Profile with smart validation
class UserProfile {
  private _email: string = "";
  private _age: number = 0;
  private _username: string = "";
  private _bio: string = "";
  
  constructor(username: string, email: string, age: number) {
    this.username = username;
    this.email = email;
    this.age = age;
  }
  
  // ๐Ÿ“ง Email with validation
  get email(): string {
    return this._email;
  }
  
  set email(value: string) {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (!emailRegex.test(value)) {
      throw new Error("โŒ Invalid email format");
    }
    this._email = value;
    console.log(`๐Ÿ“ง Email updated to: ${value}`);
  }
  
  // ๐ŸŽ‚ Age with validation
  get age(): number {
    return this._age;
  }
  
  set age(value: number) {
    if (value < 0 || value > 150) {
      throw new Error("โŒ Age must be between 0 and 150");
    }
    this._age = value;
    console.log(`๐ŸŽ‚ Age updated to: ${value}`);
  }
  
  // ๐Ÿ‘ค Username with validation
  get username(): string {
    return this._username;
  }
  
  set username(value: string) {
    if (value.length < 3 || value.length > 20) {
      throw new Error("โŒ Username must be 3-20 characters");
    }
    if (!/^[a-zA-Z0-9_]+$/.test(value)) {
      throw new Error("โŒ Username can only contain letters, numbers, and underscores");
    }
    this._username = value;
    console.log(`๐Ÿ‘ค Username updated to: ${value}`);
  }
  
  // ๐Ÿ“ Bio with length limit
  get bio(): string {
    return this._bio;
  }
  
  set bio(value: string) {
    if (value.length > 500) {
      throw new Error("โŒ Bio cannot exceed 500 characters");
    }
    this._bio = value;
    console.log(`๐Ÿ“ Bio updated (${value.length} characters)`);
  }
  
  // ๐ŸŽฏ Computed properties
  get ageGroup(): string {
    if (this._age < 13) return "๐Ÿ‘ถ Child";
    if (this._age < 20) return "๐Ÿง’ Teenager";
    if (this._age < 65) return "๐Ÿง‘ Adult";
    return "๐Ÿ‘ด Senior";
  }
  
  get isAdult(): boolean {
    return this._age >= 18;
  }
  
  get profileCompleteness(): number {
    let score = 0;
    if (this._username) score += 25;
    if (this._email) score += 25;
    if (this._age > 0) score += 25;
    if (this._bio) score += 25;
    return score;
  }
  
  get profileStatus(): string {
    const completeness = this.profileCompleteness;
    if (completeness === 100) return "โœ… Complete";
    if (completeness >= 75) return "๐ŸŒŸ Nearly Complete";
    if (completeness >= 50) return "โšก Partial";
    return "๐Ÿšง Incomplete";
  }
  
  // ๐Ÿ“Š Profile summary
  getProfileSummary(): string {
    return `
๐Ÿ‘ค ${this._username} (${this.ageGroup})
๐Ÿ“ง ${this._email}
๐ŸŽ‚ ${this._age} years old
๐Ÿ“Š Profile: ${this.profileStatus} (${this.profileCompleteness}%)
๐Ÿ“ Bio: ${this._bio || "No bio available"}
    `.trim();
  }
}

// ๐Ÿš€ Create and test user profile
const user = new UserProfile("john_doe", "[email protected]", 25);
user.bio = "TypeScript developer who loves building amazing applications! ๐Ÿš€";

console.log(user.getProfileSummary());
console.log(`Adult status: ${user.isAdult}`);
console.log(`Age group: ${user.ageGroup}`);

๐Ÿ›๏ธ Static Members

๐Ÿ”ง Static Properties and Methods

// ๐Ÿฆ Bank Account with static utilities
class BankAccount {
  private static nextAccountNumber: number = 1000;
  private static readonly BANK_NAME = "TypeScript Bank";
  private static readonly INTEREST_RATE = 0.02;
  private static accounts: BankAccount[] = [];
  
  private accountNumber: string;
  private balance: number;
  private accountHolder: string;
  private createdAt: Date;
  
  constructor(accountHolder: string, initialDeposit: number = 0) {
    this.accountNumber = BankAccount.generateAccountNumber();
    this.accountHolder = accountHolder;
    this.balance = initialDeposit;
    this.createdAt = new Date();
    
    // ๐Ÿ“Š Add to static registry
    BankAccount.accounts.push(this);
    
    console.log(`๐Ÿฆ Account ${this.accountNumber} created for ${accountHolder}`);
  }
  
  // ๐Ÿ”ข Static method to generate account numbers
  private static generateAccountNumber(): string {
    return `ACC${BankAccount.nextAccountNumber++}`;
  }
  
  // ๐Ÿ›๏ธ Static bank information
  static getBankInfo(): string {
    return `๐Ÿฆ ${BankAccount.BANK_NAME} - Serving ${BankAccount.accounts.length} customers`;
  }
  
  // ๐Ÿ“Š Static account statistics
  static getAccountStats(): {
    totalAccounts: number;
    totalDeposits: number;
    averageBalance: number;
  } {
    const totalAccounts = BankAccount.accounts.length;
    const totalDeposits = BankAccount.accounts.reduce((sum, acc) => sum + acc.balance, 0);
    const averageBalance = totalAccounts > 0 ? totalDeposits / totalAccounts : 0;
    
    return {
      totalAccounts,
      totalDeposits,
      averageBalance
    };
  }
  
  // ๐Ÿ” Static method to find account
  static findAccount(accountNumber: string): BankAccount | undefined {
    return BankAccount.accounts.find(acc => acc.accountNumber === accountNumber);
  }
  
  // ๐Ÿ’ฐ Static interest calculator
  static calculateInterest(balance: number, months: number): number {
    return balance * BankAccount.INTEREST_RATE * (months / 12);
  }
  
  // ๐Ÿ“ˆ Static method to get top accounts
  static getTopAccounts(count: number = 5): BankAccount[] {
    return BankAccount.accounts
      .sort((a, b) => b.balance - a.balance)
      .slice(0, count);
  }
  
  // ๐Ÿ’ณ Instance methods
  deposit(amount: number): void {
    if (amount <= 0) {
      console.log("โŒ Deposit amount must be positive");
      return;
    }
    
    this.balance += amount;
    console.log(`๐Ÿ’ฐ Deposited $${amount}. New balance: $${this.balance}`);
  }
  
  withdraw(amount: number): boolean {
    if (amount <= 0) {
      console.log("โŒ Withdrawal amount must be positive");
      return false;
    }
    
    if (amount > this.balance) {
      console.log("โŒ Insufficient funds");
      return false;
    }
    
    this.balance -= amount;
    console.log(`๐Ÿ’ธ Withdrew $${amount}. Remaining balance: $${this.balance}`);
    return true;
  }
  
  getAccountInfo(): string {
    return `๐Ÿฆ Account: ${this.accountNumber} | Holder: ${this.accountHolder} | Balance: $${this.balance}`;
  }
  
  // ๐Ÿ“Š Instance method using static method
  calculateFutureValue(months: number): number {
    const interest = BankAccount.calculateInterest(this.balance, months);
    return this.balance + interest;
  }
}

// ๐Ÿš€ Test static and instance methods
console.log(BankAccount.getBankInfo());

// Create accounts
const account1 = new BankAccount("Alice Johnson", 1000);
const account2 = new BankAccount("Bob Smith", 2500);
const account3 = new BankAccount("Carol Davis", 1500);

// Instance operations
account1.deposit(500);
account2.withdraw(200);

// Static operations
console.log("\n๐Ÿ“Š Bank Statistics:");
const stats = BankAccount.getAccountStats();
console.log(`Total Accounts: ${stats.totalAccounts}`);
console.log(`Total Deposits: $${stats.totalDeposits}`);
console.log(`Average Balance: $${stats.averageBalance.toFixed(2)}`);

// Find account
const foundAccount = BankAccount.findAccount("ACC1001");
console.log(`\n๐Ÿ” Found: ${foundAccount?.getAccountInfo()}`);

// Top accounts
console.log("\n๐Ÿ† Top Accounts:");
BankAccount.getTopAccounts(2).forEach(acc => {
  console.log(acc.getAccountInfo());
});

// Interest calculation
const futureValue = account1.calculateFutureValue(12);
console.log(`\n๐Ÿ“ˆ Account 1 value after 12 months: $${futureValue.toFixed(2)}`);

โš ๏ธ Common Pitfalls and Solutions

๐Ÿ˜ฑ Pitfall 1: Uninitialized Properties

// โŒ Problematic - uninitialized properties
class BadUser {
  name: string;        // โŒ Not initialized
  email: string;       // โŒ Not initialized
  age: number;         // โŒ Not initialized
  
  constructor() {
    // Properties not set - will be undefined!
  }
}

// โœ… Better - initialize properties
class GoodUser {
  name: string = "";           // โœ… Default value
  email: string = "";          // โœ… Default value
  age: number = 0;             // โœ… Default value
  
  constructor(name?: string, email?: string, age?: number) {
    if (name) this.name = name;
    if (email) this.email = email;
    if (age) this.age = age;
  }
}

// โœ… Best - parameter properties
class BestUser {
  constructor(
    public name: string = "",
    public email: string = "",
    public age: number = 0
  ) {}
}

๐Ÿคฏ Pitfall 2: Incorrect โ€˜thisโ€™ binding

class EventHandler {
  private count: number = 0;
  
  // โŒ Problematic - 'this' can be lost
  handleClick() {
    this.count++;
    console.log(`Clicked ${this.count} times`);
  }
  
  // โœ… Solution - arrow function preserves 'this'
  handleClickSafe = () => {
    this.count++;
    console.log(`Clicked ${this.count} times`);
  }
  
  // โœ… Alternative - bind in constructor
  constructor() {
    this.handleClick = this.handleClick.bind(this);
  }
}

๐Ÿ”’ Pitfall 3: Breaking encapsulation

// โŒ Bad - exposes internal state
class BadWallet {
  public balance: number = 0;  // โŒ Direct access to balance
  
  addMoney(amount: number) {
    this.balance += amount;
  }
}

// โœ… Good - controlled access
class GoodWallet {
  private balance: number = 0;  // ๐Ÿ”’ Private balance
  
  deposit(amount: number): void {
    if (amount <= 0) {
      throw new Error("โŒ Amount must be positive");
    }
    this.balance += amount;
  }
  
  getBalance(): number {
    return this.balance;
  }
}

๐Ÿ› ๏ธ Best Practices

  1. ๐ŸŽฏ Use Parameter Properties: Reduce boilerplate with constructor parameters
  2. ๐Ÿ”’ Prefer Private: Make properties private by default, expose through methods
  3. โœจ Validate in Setters: Use setters for validation and business logic
  4. ๐Ÿ“Š Static for Utilities: Use static methods for class-level operations
  5. ๐ŸŽจ Fluent Interfaces: Enable method chaining for better APIs
  6. ๐Ÿ”„ Immutable Patterns: Consider readonly properties for data integrity

๐Ÿงช Hands-On Exercise

๐ŸŽฏ Challenge: Build a Social Media Post System

Create a comprehensive social media post management system:

๐Ÿ“‹ Requirements:

  • ๐Ÿ“ Post class with content, author, timestamp, and engagement metrics
  • ๐Ÿ‘ค User class with profile information and post history
  • ๐Ÿข SocialMedia class to manage posts and users
  • ๐Ÿ’ฌ Comment system with nested replies
  • ๐Ÿ‘ Like/dislike functionality with user tracking
  • ๐Ÿ” Search and filtering capabilities

๐Ÿš€ Bonus Features:

  • Hashtag extraction and trending topics
  • Post scheduling system
  • Analytics dashboard
  • Content moderation system

๐Ÿ’ก Solution

๐Ÿ” Click to see solution
// ๐Ÿ‘ค User class with profile management
class User {
  private static nextUserId: number = 1;
  private _followers: Set<string> = new Set();
  private _following: Set<string> = new Set();
  
  constructor(
    public readonly id: string = `user_${User.nextUserId++}`,
    private _username: string,
    private _email: string,
    private _displayName: string,
    private _bio: string = ""
  ) {}
  
  // ๐Ÿ‘ค Username with validation
  get username(): string {
    return this._username;
  }
  
  set username(value: string) {
    if (value.length < 3 || value.length > 20) {
      throw new Error("โŒ Username must be 3-20 characters");
    }
    this._username = value;
  }
  
  get displayName(): string {
    return this._displayName;
  }
  
  set displayName(value: string) {
    if (value.length > 50) {
      throw new Error("โŒ Display name too long");
    }
    this._displayName = value;
  }
  
  get bio(): string {
    return this._bio;
  }
  
  set bio(value: string) {
    if (value.length > 200) {
      throw new Error("โŒ Bio too long (max 200 characters)");
    }
    this._bio = value;
  }
  
  // ๐Ÿ‘ฅ Social features
  get followerCount(): number {
    return this._followers.size;
  }
  
  get followingCount(): number {
    return this._following.size;
  }
  
  follow(userId: string): void {
    this._following.add(userId);
  }
  
  unfollow(userId: string): void {
    this._following.delete(userId);
  }
  
  addFollower(userId: string): void {
    this._followers.add(userId);
  }
  
  removeFollower(userId: string): void {
    this._followers.delete(userId);
  }
  
  isFollowing(userId: string): boolean {
    return this._following.has(userId);
  }
  
  getProfile(): string {
    return `
๐Ÿ‘ค @${this._username} (${this._displayName})
๐Ÿ“ ${this._bio || "No bio"}
๐Ÿ‘ฅ ${this.followerCount} followers โ€ข ${this.followingCount} following
    `.trim();
  }
}

// ๐Ÿ’ฌ Comment class
class Comment {
  private static nextCommentId: number = 1;
  private _likes: Set<string> = new Set();
  private _replies: Comment[] = [];
  
  constructor(
    public readonly id: string = `comment_${Comment.nextCommentId++}`,
    public readonly authorId: string,
    public readonly content: string,
    public readonly timestamp: Date = new Date(),
    public readonly parentCommentId: string | null = null
  ) {}
  
  get likeCount(): number {
    return this._likes.size;
  }
  
  get replyCount(): number {
    return this._replies.length;
  }
  
  like(userId: string): void {
    this._likes.add(userId);
  }
  
  unlike(userId: string): void {
    this._likes.delete(userId);
  }
  
  isLikedBy(userId: string): boolean {
    return this._likes.has(userId);
  }
  
  addReply(reply: Comment): void {
    this._replies.push(reply);
  }
  
  getReplies(): readonly Comment[] {
    return [...this._replies];
  }
}

// ๐Ÿ“ Social Media Post class
class SocialMediaPost {
  private static nextPostId: number = 1;
  private _likes: Set<string> = new Set();
  private _comments: Comment[] = [];
  private _hashtags: string[] = [];
  private _mentions: string[] = [];
  
  constructor(
    public readonly id: string = `post_${SocialMediaPost.nextPostId++}`,
    public readonly authorId: string,
    private _content: string,
    public readonly timestamp: Date = new Date(),
    public readonly imageUrl?: string
  ) {
    this.extractHashtagsAndMentions();
  }
  
  get content(): string {
    return this._content;
  }
  
  set content(value: string) {
    if (value.length > 280) {
      throw new Error("โŒ Post too long (max 280 characters)");
    }
    this._content = value;
    this.extractHashtagsAndMentions();
  }
  
  get likeCount(): number {
    return this._likes.size;
  }
  
  get commentCount(): number {
    return this._comments.length;
  }
  
  get hashtags(): readonly string[] {
    return [...this._hashtags];
  }
  
  get mentions(): readonly string[] {
    return [...this._mentions];
  }
  
  // ๐Ÿ“Š Engagement metrics
  get engagementScore(): number {
    return this.likeCount + this.commentCount * 2;
  }
  
  get timeAgo(): string {
    const now = new Date();
    const diffMs = now.getTime() - this.timestamp.getTime();
    const diffMins = Math.floor(diffMs / 60000);
    const diffHours = Math.floor(diffMs / 3600000);
    const diffDays = Math.floor(diffMs / 86400000);
    
    if (diffMins < 60) return `${diffMins}m`;
    if (diffHours < 24) return `${diffHours}h`;
    return `${diffDays}d`;
  }
  
  // ๐Ÿ’ Like functionality
  like(userId: string): void {
    this._likes.add(userId);
  }
  
  unlike(userId: string): void {
    this._likes.delete(userId);
  }
  
  isLikedBy(userId: string): boolean {
    return this._likes.has(userId);
  }
  
  // ๐Ÿ’ฌ Comment functionality
  addComment(comment: Comment): void {
    this._comments.push(comment);
  }
  
  getComments(): readonly Comment[] {
    return [...this._comments];
  }
  
  // ๐Ÿ” Extract hashtags and mentions
  private extractHashtagsAndMentions(): void {
    const hashtagRegex = /#\w+/g;
    const mentionRegex = /@\w+/g;
    
    this._hashtags = (this._content.match(hashtagRegex) || [])
      .map(tag => tag.substring(1).toLowerCase());
    
    this._mentions = (this._content.match(mentionRegex) || [])
      .map(mention => mention.substring(1).toLowerCase());
  }
  
  // ๐Ÿ“ฑ Format for display
  getFormattedPost(author: User): string {
    const heartIcon = this.likeCount > 0 ? "๐Ÿ’–" : "๐Ÿค";
    const imageInfo = this.imageUrl ? " ๐Ÿ“ธ" : "";
    
    return `
๐Ÿ‘ค ${author.displayName} @${author.username} โ€ข ${this.timeAgo}
๐Ÿ“ ${this._content}${imageInfo}
${heartIcon} ${this.likeCount} ๐Ÿ’ฌ ${this.commentCount} ๐Ÿ“Š ${this.engagementScore}
${this._hashtags.length > 0 ? `๐Ÿท๏ธ ${this._hashtags.map(tag => `#${tag}`).join(' ')}` : ''}
    `.trim();
  }
}

// ๐Ÿข Social Media Platform class
class SocialMediaPlatform {
  private users: Map<string, User> = new Map();
  private posts: Map<string, SocialMediaPost> = new Map();
  private usernameLookup: Map<string, string> = new Map();
  
  // ๐Ÿ‘ค User management
  registerUser(username: string, email: string, displayName: string): User {
    if (this.usernameLookup.has(username.toLowerCase())) {
      throw new Error("โŒ Username already taken");
    }
    
    const user = new User(undefined, username, email, displayName);
    this.users.set(user.id, user);
    this.usernameLookup.set(username.toLowerCase(), user.id);
    
    console.log(`๐ŸŽ‰ Welcome ${displayName}! Your account has been created.`);
    return user;
  }
  
  getUserById(userId: string): User | undefined {
    return this.users.get(userId);
  }
  
  getUserByUsername(username: string): User | undefined {
    const userId = this.usernameLookup.get(username.toLowerCase());
    return userId ? this.users.get(userId) : undefined;
  }
  
  // ๐Ÿ“ Post management
  createPost(authorId: string, content: string, imageUrl?: string): SocialMediaPost {
    const author = this.users.get(authorId);
    if (!author) {
      throw new Error("โŒ Author not found");
    }
    
    const post = new SocialMediaPost(undefined, authorId, content, undefined, imageUrl);
    this.posts.set(post.id, post);
    
    console.log(`๐Ÿ“ New post by @${author.username}: "${content.substring(0, 50)}..."`);
    return post;
  }
  
  getPost(postId: string): SocialMediaPost | undefined {
    return this.posts.get(postId);
  }
  
  // ๐Ÿ” Search and filtering
  searchPosts(query: string): SocialMediaPost[] {
    const results: SocialMediaPost[] = [];
    const searchTerm = query.toLowerCase();
    
    for (const post of this.posts.values()) {
      if (post.content.toLowerCase().includes(searchTerm) ||
          post.hashtags.some(tag => tag.includes(searchTerm))) {
        results.push(post);
      }
    }
    
    return results.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
  }
  
  getPostsByHashtag(hashtag: string): SocialMediaPost[] {
    const results: SocialMediaPost[] = [];
    const normalizedTag = hashtag.toLowerCase().replace('#', '');
    
    for (const post of this.posts.values()) {
      if (post.hashtags.includes(normalizedTag)) {
        results.push(post);
      }
    }
    
    return results.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
  }
  
  // ๐Ÿ“Š Analytics
  getTrendingHashtags(limit: number = 10): Array<{tag: string, count: number}> {
    const tagCounts = new Map<string, number>();
    
    for (const post of this.posts.values()) {
      post.hashtags.forEach(tag => {
        tagCounts.set(tag, (tagCounts.get(tag) || 0) + 1);
      });
    }
    
    return Array.from(tagCounts.entries())
      .map(([tag, count]) => ({ tag, count }))
      .sort((a, b) => b.count - a.count)
      .slice(0, limit);
  }
  
  getTopPosts(limit: number = 10): SocialMediaPost[] {
    return Array.from(this.posts.values())
      .sort((a, b) => b.engagementScore - a.engagementScore)
      .slice(0, limit);
  }
  
  // ๐Ÿ“ˆ Platform statistics
  getStats(): {
    totalUsers: number;
    totalPosts: number;
    totalEngagements: number;
    averageEngagementPerPost: number;
  } {
    const totalUsers = this.users.size;
    const totalPosts = this.posts.size;
    const totalEngagements = Array.from(this.posts.values())
      .reduce((sum, post) => sum + post.engagementScore, 0);
    const averageEngagementPerPost = totalPosts > 0 ? totalEngagements / totalPosts : 0;
    
    return {
      totalUsers,
      totalPosts,
      totalEngagements,
      averageEngagementPerPost
    };
  }
  
  // ๐Ÿ“ฑ Display feed
  displayFeed(userId: string, limit: number = 10): void {
    const user = this.users.get(userId);
    if (!user) {
      console.log("โŒ User not found");
      return;
    }
    
    const posts = Array.from(this.posts.values())
      .sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime())
      .slice(0, limit);
    
    console.log(`\n๐Ÿ“ฑ Feed for @${user.username}:`);
    console.log('='.repeat(50));
    
    posts.forEach(post => {
      const author = this.users.get(post.authorId)!;
      console.log(post.getFormattedPost(author));
      console.log('โ”€'.repeat(30));
    });
  }
}

// ๐Ÿš€ Demo the social media platform
const platform = new SocialMediaPlatform();

// Register users
const alice = platform.registerUser("alice_codes", "[email protected]", "Alice Johnson");
const bob = platform.registerUser("bob_dev", "[email protected]", "Bob Smith");
const carol = platform.registerUser("carol_design", "[email protected]", "Carol Davis");

// Create posts
platform.createPost(alice.id, "Just deployed my first TypeScript app! ๐Ÿš€ #typescript #coding #webdev");
platform.createPost(bob.id, "Working on some cool #reactjs components today! @alice_codes check this out ๐Ÿ‘€");
platform.createPost(carol.id, "New UI design for our app is ready! ๐ŸŽจ #design #ux #figma");
platform.createPost(alice.id, "TypeScript classes are so powerful! ๐Ÿ’ช #typescript #oop");

// Interact with posts
const posts = Array.from(platform['posts'].values());
posts[0].like(bob.id);
posts[0].like(carol.id);
posts[1].like(alice.id);

// Add comments
const comment1 = new Comment(undefined, bob.id, "Amazing work! ๐ŸŽ‰");
posts[0].addComment(comment1);

// Display feed
platform.displayFeed(alice.id);

// Show trending hashtags
console.log("\n๐Ÿ”ฅ Trending Hashtags:");
platform.getTrendingHashtags(5).forEach(({tag, count}) => {
  console.log(`#${tag}: ${count} posts`);
});

// Platform stats
console.log("\n๐Ÿ“Š Platform Statistics:");
const stats = platform.getStats();
console.log(`๐Ÿ‘ฅ Users: ${stats.totalUsers}`);
console.log(`๐Ÿ“ Posts: ${stats.totalPosts}`);
console.log(`๐Ÿ’ซ Total Engagements: ${stats.totalEngagements}`);
console.log(`๐Ÿ“ˆ Avg Engagement/Post: ${stats.averageEngagementPerPost.toFixed(1)}`);

๐ŸŽ“ Key Takeaways

Fantastic! Youโ€™ve mastered TypeScript class properties and methods! Hereโ€™s what you can now do:

  • โœ… Create flexible properties with various initialization patterns ๐Ÿ”ง
  • โœ… Use parameter properties for cleaner, more concise code โšก
  • โœ… Implement method overloading for versatile APIs ๐ŸŽฏ
  • โœ… Build fluent interfaces with method chaining ๐Ÿ”„
  • โœ… Control access with getters and setters ๐Ÿ”
  • โœ… Utilize static members for class-level functionality ๐Ÿ›๏ธ

Remember: Well-designed class members are the foundation of maintainable, scalable applications! ๐Ÿš€

๐Ÿค Next Steps

Incredible progress! ๐ŸŽ‰ Youโ€™ve become a class member expert!

Your journey continues with:

  1. ๐Ÿ’ป Complete the social media platform exercise above
  2. ๐Ÿ—๏ธ Refactor existing code to use advanced patterns
  3. ๐Ÿ“š Next tutorial: Constructors in TypeScript - Initializing Objects
  4. ๐ŸŒŸ Experiment with your own class-based projects!

Remember: Great code is built one property and method at a time. Keep crafting, keep learning! ๐Ÿ’ช


Happy coding! ๐ŸŽ‰๐Ÿ”งโœจ