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:
- Encapsulation ๐: Control access to your data
- Flexibility ๐คธ: Adapt to changing requirements
- Maintainability ๐ ๏ธ: Easy to update and debug
- Performance โก: Optimize memory usage and execution
- 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
- ๐ฏ Use Parameter Properties: Reduce boilerplate with constructor parameters
- ๐ Prefer Private: Make properties private by default, expose through methods
- โจ Validate in Setters: Use setters for validation and business logic
- ๐ Static for Utilities: Use static methods for class-level operations
- ๐จ Fluent Interfaces: Enable method chaining for better APIs
- ๐ 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:
- ๐ป Complete the social media platform exercise above
- ๐๏ธ Refactor existing code to use advanced patterns
- ๐ Next tutorial: Constructors in TypeScript - Initializing Objects
- ๐ 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! ๐๐งโจ