TypeScript Classes
TypeScript enhances JavaScript's class syntax with powerful features like access modifiers, abstract classes, parameter properties, and strong typing for all class members. This enables building robust object-oriented applications with clear encapsulation, inheritance hierarchies, and compile-time type safety.
Classes in TypeScript provide a blueprint for creating objects with defined properties and methods, while the type system ensures that class contracts are properly enforced throughout your application.
Basic Class Definition
TypeScript classes include type annotations for properties and methods, providing compile-time safety and better IDE support.
class User {
// Property declarations with types
id: number;
name: string;
email: string;
isActive: boolean;
constructor(id: number, name: string, email: string) {
this.id = id;
this.name = name;
this.email = email;
this.isActive = true;
}
// Method with typed parameters and return type
getDisplayName(): string {
return `${this.name} (${this.email})`;
}
toggleStatus(): void {
this.isActive = !this.isActive;
}
updateEmail(newEmail: string): boolean {
if (newEmail.includes('@')) {
this.email = newEmail;
return true;
}
return false;
}
}
// Creating instances
const user = new User(1, "Alice Johnson", "[email protected]");
console.log(user.getDisplayName()); // "Alice Johnson ([email protected])"
Access Modifiers
TypeScript provides three access modifiers to control the visibility of class members.
class BankAccount {
public accountNumber: string; // Accessible everywhere
private balance: number; // Only accessible within this class
protected ownerName: string; // Accessible within this class and subclasses
constructor(accountNumber: string, ownerName: string, initialBalance: number = 0) {
this.accountNumber = accountNumber;
this.ownerName = ownerName;
this.balance = initialBalance;
}
// Public method
public getAccountInfo(): string {
return `Account: ${this.accountNumber}, Owner: ${this.ownerName}`;
}
// Private method
private validateAmount(amount: number): boolean {
return amount > 0 && amount <= this.balance;
}
// Public method using private method
public withdraw(amount: number): boolean {
if (this.validateAmount(amount)) {
this.balance -= amount;
return true;
}
return false;
}
public deposit(amount: number): void {
if (amount > 0) {
this.balance += amount;
}
}
// Protected method for subclasses
protected getBalance(): number {
return this.balance;
}
}
const account = new BankAccount("12345", "John Doe", 1000);
console.log(account.accountNumber); // OK: public
// console.log(account.balance); // Error: private
// account.validateAmount(100); // Error: private method
Parameter Properties
TypeScript provides a shorthand syntax for creating and initializing class properties through constructor parameters.
class Product {
// Traditional approach
/*
private id: number;
public name: string;
protected price: number;
constructor(id: number, name: string, price: number) {
this.id = id;
this.name = name;
this.price = price;
}
*/
// Parameter properties shorthand
constructor(
private id: number,
public name: string,
protected price: number,
public readonly category: string
) {
// Properties are automatically created and initialized
}
public getInfo(): string {
return `${this.name} - $${this.price}`;
}
protected getId(): number {
return this.id;
}
}
const laptop = new Product(1, "MacBook Pro", 2499, "Electronics");
console.log(laptop.name); // OK: public
console.log(laptop.category); // OK: readonly public
// laptop.category = "New Category"; // Error: readonly
// console.log(laptop.id); // Error: private
Inheritance
Classes can extend other classes, inheriting their properties and methods while adding new functionality.
class Animal {
protected name: string;
protected age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
public speak(): string {
return `${this.name} makes a sound`;
}
public getAge(): number {
return this.age;
}
}
class Dog extends Animal {
private breed: string;
constructor(name: string, age: number, breed: string) {
super(name, age); // Call parent constructor
this.breed = breed;
}
// Override parent method
public speak(): string {
return `${this.name} barks loudly!`;
}
// Add new method
public getBreed(): string {
return this.breed;
}
// Method using protected property from parent
public introduce(): string {
return `This is ${this.name}, a ${this.age}-year-old ${this.breed}`;
}
}
class Cat extends Animal {
private isIndoor: boolean;
constructor(name: string, age: number, isIndoor: boolean = true) {
super(name, age);
this.isIndoor = isIndoor;
}
public speak(): string {
return `${this.name} meows softly`;
}
public isIndoorCat(): boolean {
return this.isIndoor;
}
}
const dog = new Dog("Buddy", 3, "Golden Retriever");
const cat = new Cat("Whiskers", 2, false);
console.log(dog.speak()); // "Buddy barks loudly!"
console.log(cat.speak()); // "Whiskers meows softly"
console.log(dog.introduce()); // "This is Buddy, a 3-year-old Golden Retriever"
Abstract Classes
Abstract classes cannot be instantiated directly and serve as base classes for other classes.
abstract class Shape {
protected name: string;
constructor(name: string) {
this.name = name;
}
// Abstract method - must be implemented by subclasses
abstract getArea(): number;
abstract getPerimeter(): number;
// Concrete method - available to all subclasses
public displayInfo(): string {
return `${this.name}: Area = ${this.getArea()}, Perimeter = ${this.getPerimeter()}`;
}
// Protected method for subclasses
protected getName(): string {
return this.name;
}
}
class Rectangle extends Shape {
constructor(private width: number, private height: number) {
super("Rectangle");
}
getArea(): number {
return this.width * this.height;
}
getPerimeter(): number {
return 2 * (this.width + this.height);
}
public getDimensions(): string {
return `${this.width} x ${this.height}`;
}
}
class Circle extends Shape {
constructor(private radius: number) {
super("Circle");
}
getArea(): number {
return Math.PI * this.radius ** 2;
}
getPerimeter(): number {
return 2 * Math.PI * this.radius;
}
public getRadius(): number {
return this.radius;
}
}
// const shape = new Shape("test"); // Error: Cannot instantiate abstract class
const rectangle = new Rectangle(10, 5);
const circle = new Circle(7);
console.log(rectangle.displayInfo()); // Rectangle: Area = 50, Perimeter = 30
console.log(circle.displayInfo()); // Circle: Area = 153.94, Perimeter = 43.98
Static Members
Static properties and methods belong to the class itself rather than to instances.
class MathUtilities {
static readonly PI: number = 3.14159;
static readonly E: number = 2.71828;
private static instanceCount: number = 0;
constructor() {
MathUtilities.instanceCount++;
}
static add(a: number, b: number): number {
return a + b;
}
static multiply(a: number, b: number): number {
return a * b;
}
static getCircleArea(radius: number): number {
return MathUtilities.PI * radius ** 2;
}
static getInstanceCount(): number {
return MathUtilities.instanceCount;
}
// Instance method accessing static member
getPI(): number {
return MathUtilities.PI;
}
}
// Using static members without instantiation
console.log(MathUtilities.add(5, 3)); // 8
console.log(MathUtilities.getCircleArea(5)); // 78.54
console.log(MathUtilities.PI); // 3.14159
// Creating instances affects static counter
const math1 = new MathUtilities();
const math2 = new MathUtilities();
console.log(MathUtilities.getInstanceCount()); // 2
Getters and Setters
Accessors provide controlled access to class properties with validation and computed values.
class Temperature {
private _celsius: number;
constructor(celsius: number) {
this._celsius = celsius;
}
// Getter for celsius
get celsius(): number {
return this._celsius;
}
// Setter for celsius with validation
set celsius(value: number) {
if (value < -273.15) {
throw new Error("Temperature cannot be below absolute zero");
}
this._celsius = value;
}
// Computed property - fahrenheit
get fahrenheit(): number {
return (this._celsius * 9/5) + 32;
}
set fahrenheit(value: number) {
this.celsius = (value - 32) * 5/9;
}
// Computed property - kelvin
get kelvin(): number {
return this._celsius + 273.15;
}
set kelvin(value: number) {
this.celsius = value - 273.15;
}
// Read-only computed property
get description(): string {
if (this._celsius < 0) return "Below freezing";
if (this._celsius < 10) return "Cold";
if (this._celsius < 25) return "Cool";
if (this._celsius < 35) return "Warm";
return "Hot";
}
}
const temp = new Temperature(25);
console.log(temp.celsius); // 25
console.log(temp.fahrenheit); // 77
console.log(temp.kelvin); // 298.15
console.log(temp.description); // "Warm"
temp.fahrenheit = 86;
console.log(temp.celsius); // 30
Generic Classes
Classes can be generic, allowing them to work with different types while maintaining type safety.
class Container<T> {
private items: T[] = [];
add(item: T): void {
this.items.push(item);
}
remove(index: number): T | undefined {
return this.items.splice(index, 1)[0];
}
get(index: number): T | undefined {
return this.items[index];
}
getAll(): T[] {
return [...this.items];
}
find(predicate: (item: T) => boolean): T | undefined {
return this.items.find(predicate);
}
get length(): number {
return this.items.length;
}
}
// Usage with different types
const stringContainer = new Container<string>();
stringContainer.add("hello");
stringContainer.add("world");
console.log(stringContainer.get(0)); // "hello"
const numberContainer = new Container<number>();
numberContainer.add(1);
numberContainer.add(2);
numberContainer.add(3);
console.log(numberContainer.getAll()); // [1, 2, 3]
interface User {
id: number;
name: string;
}
const userContainer = new Container<User>();
userContainer.add({ id: 1, name: "Alice" });
userContainer.add({ id: 2, name: "Bob" });
const alice = userContainer.find(user => user.name === "Alice");
Class Implementing Interfaces
Classes can implement interfaces to ensure they conform to specific contracts.
interface Drawable {
draw(): void;
}
interface Movable {
x: number;
y: number;
move(dx: number, dy: number): void;
}
interface Resizable {
width: number;
height: number;
resize(width: number, height: number): void;
}
class GameObject implements Drawable, Movable {
constructor(public x: number, public y: number) {}
draw(): void {
console.log(`Drawing at (${this.x}, ${this.y})`);
}
move(dx: number, dy: number): void {
this.x += dx;
this.y += dy;
}
}
class Rectangle implements Drawable, Movable, Resizable {
constructor(
public x: number,
public y: number,
public width: number,
public height: number
) {}
draw(): void {
console.log(`Drawing rectangle at (${this.x}, ${this.y}) with size ${this.width}x${this.height}`);
}
move(dx: number, dy: number): void {
this.x += dx;
this.y += dy;
}
resize(width: number, height: number): void {
this.width = width;
this.height = height;
}
}
const gameObject = new GameObject(10, 20);
const rectangle = new Rectangle(0, 0, 100, 50);
gameObject.draw();
rectangle.draw();
rectangle.resize(200, 100);
TypeScript classes provide a robust foundation for object-oriented programming with strong typing, encapsulation, and inheritance. They enable building maintainable applications with clear contracts and compile-time safety while leveraging familiar object-oriented patterns.