Classes in TypeScript provide a powerful way to define blueprints for creating objects with properties and methods. They combine the familiar class-based syntax from languages like Java or C# with JavaScript's prototype-based inheritance, all while adding TypeScript's type safety.
classPerson { name:string; age:number;constructor(name:string, age:number) {this.name = name;this.age = age; }greet():string {return`Hello, my name is ${this.name} and I'm ${this.age} years old.`; }}// Creating an instance of the classconstalice=newPerson("Alice",28);console.log(alice.greet()); // "Hello, my name is Alice and I'm 28 years old."
This example shows the fundamental components of a class:
Properties (name and age)
Constructor method that initializes the properties
Instance method (greet())
Creating an instance of the class using the new keyword
TypeScript provides access modifiers to control the visibility of class members:
Access Modifiers
classBankAccount {// Public - accessible from anywhere (default)public accountNumber:string;// Private - only accessible within the classprivate balance:number;// Protected - accessible within the class and its subclassesprotected owner:string;constructor(accountNumber:string, initialBalance:number, owner:string) {this.accountNumber = accountNumber;this.balance = initialBalance;this.owner = owner; }// Public methodpublicdeposit(amount:number):void {if (amount >0) {this.balance += amount;console.log(`Deposited ${amount}. New balance: ${this.balance}`); } }// Public methodpublicgetBalance():number {returnthis.balance; }}constaccount=newBankAccount("123456",1000,"Alice");account.deposit(500); // Deposited 500. New balance: 1500console.log(account.getBalance()); // 1500console.log(account.accountNumber); // "123456"// These would cause compiler errors:// console.log(account.balance); // Error: Property 'balance' is private// console.log(account.owner); // Error: Property 'owner' is protected
Access modifiers help enforce encapsulation:
public: Members are accessible from anywhere (default if no modifier is specified)
private: Members are only accessible within the class where they're defined
protected: Members are accessible within the class and any subclasses
TypeScript provides a concise way to define and initialize class members in the constructor:
Parameter Properties
classPerson {// Parameter properties - shorthand that creates and initializes class membersconstructor(public name:string,public age:number,private ssn:string ) {// No need to manually assign properties }greet():string {return`Hello, my name is ${this.name} and I'm ${this.age} years old.`; }}constbob=newPerson("Bob",32,"123-45-6789");console.log(bob.name); // "Bob"console.log(bob.age); // 32// console.log(bob.ssn); // Error: Property 'ssn' is private
This syntax reduces boilerplate code by combining the declaration and initialization of class properties.
You can use the readonly modifier to make properties that can only be set during initialization:
Readonly Properties
classCircle {readonly radius:number;constructor(radius:number) {this.radius = radius; }calculateArea():number {returnMath.PI*this.radius *this.radius; }}constcircle=newCircle(5);console.log(circle.radius); // 5console.log(circle.calculateArea()); // ~78.54// This would cause a compiler error:// circle.radius = 10; // Error: Cannot assign to 'radius' because it is a read-only property
The readonly modifier ensures that a property can't be changed after initialization, providing a level of immutability.
TypeScript supports class inheritance, allowing you to extend existing classes:
Class Inheritance
// Base classclassAnimal {constructor(public name:string) {}move(distance:number=0):void {console.log(`${this.name} moved ${distance} meters.`); }}// Derived classclassDogextendsAnimal {constructor( name:string,private breed:string ) {// Call the base class constructorsuper(name); }// Override the move methodmove(distance:number=5):void {console.log(`${this.name} the ${this.breed} is running...`);// Call the base class methodsuper.move(distance); }// Add a new methodbark():void {console.log("Woof! Woof!"); }}constdog=newDog("Rex","German Shepherd");dog.move(); // "Rex the German Shepherd is running..." followed by "Rex moved 5 meters."dog.bark(); // "Woof! Woof!"
Key points about inheritance:
Use the extends keyword to create a subclass
Use super() in the constructor to call the parent class constructor
Use super.methodName() to call a parent class method
Subclasses can override methods from the parent class
Abstract classes serve as base classes that cannot be instantiated directly:
Abstract Classes
abstractclassShape {constructor(protected color:string) {}// Abstract method (must be implemented by subclasses)abstractcalculateArea():number;// Regular methoddisplayColor():void {console.log(`This shape is ${this.color}`); }}classRectangleextendsShape {constructor( color:string,private width:number,private height:number ) {super(color); }// Implement the abstract methodcalculateArea():number {returnthis.width *this.height; }}// Cannot create an instance of an abstract class// const shape = new Shape("red"); // Errorconstrectangle=newRectangle("blue",5,10);console.log(rectangle.calculateArea()); // 50rectangle.displayColor(); // "This shape is blue"
Abstract classes are useful when you want to define a common structure for related classes, but ensure that only concrete subclasses can be instantiated.
Classes can implement interfaces, ensuring they conform to a specific structure:
Implementing Interfaces
interfaceDrivable {start():void;stop():void; speed:number;}classCarimplementsDrivable { speed:number=0;start():void {console.log("Car started. Ready to drive!"); }stop():void {console.log("Car stopped.");this.speed =0; }accelerate(increment:number):void {this.speed += increment;console.log(`Car accelerating. Current speed: ${this.speed}`); }}constmyCar=newCar();myCar.start(); // "Car started. Ready to drive!"myCar.accelerate(50); // "Car accelerating. Current speed: 50"myCar.stop(); // "Car stopped."
By implementing an interface, a class guarantees that it provides all the properties and methods defined in the interface. This is a form of contract that helps ensure type safety.
classCalculator {// Method overloadsadd(a:number, b:number):number;add(a:string, b:string):string;// Implementationadd(a:number|string, b:number|string):number|string {if (typeof a ==="number"&&typeof b ==="number") {return a + b; }if (typeof a ==="string"&&typeof b ==="string") {returna.concat(b); }thrownewError("Parameters must be both numbers or both strings"); }}constcalc=newCalculator();console.log(calc.add(5,3)); // 8console.log(calc.add("Hello, ","World")); // "Hello, World"// This would cause a runtime error due to type checking:// calc.add(5, "World"); // Error: Parameters must be both numbers or both strings
Method overloading allows you to define multiple signatures for a method, providing more specific type information based on the types of arguments.
Create a Rectangle class with width and height properties and methods to calculate the area and perimeter.
Exercise 2: Class with Private Properties and Getters/Setters
Create a BankAccount class with a private _balance property and getters and setters to control access to it. Include methods for depositing and withdrawing money.
Exercise 3: Inheritance and Polymorphism
Create a base Shape class with a method to calculate area. Then create Circle and Square classes that inherit from Shape and implement the area calculation.
TypeScript classes combine the familiar syntax of traditional object-oriented programming with the powerful type system of TypeScript, providing a robust way to create well-structured, type-safe applications.
Key concepts covered in this guide:
Basic class syntax, properties, and methods
Access modifiers: public, private, and protected
Parameter properties for concise property declaration
readonly properties for immutability
Getters and setters to control access to properties
Static members that belong to the class rather than instances
Inheritance using the extends keyword
Abstract classes that serve as base classes
Implementing interfaces to ensure type conformance
Method overloading for type-specific behavior
Using generics to create flexible, reusable classes
By mastering these concepts, you'll be well-equipped to create maintainable, type-safe object-oriented code in TypeScript.