Tutorials Logic, IN info@tutorialslogic.com
Navigation
Home About Us Contact Us Blogs FAQs
Tutorials
All Tutorials
Services
Academic Projects Resume Writing Website Development
Practice
Quiz Challenge Interview Questions Certification Practice
Tools
Online Compiler JSON Formatter Regex Tester CSS Unit Converter Color Picker
Compiler Tools

TypeScript Classes: Access Modifiers, Constructors and Implements

Class Basics

TypeScript classes build on JavaScript classes and add type checking for properties, constructor parameters, method parameters, return values, visibility, and interface implementation.

Classes are useful when data and behavior naturally belong together. They are common in services, domain models, framework code, dependency injection, and object-oriented designs. For plain data with no behavior, an interface or type alias is usually simpler.

Basic Class
class Course {
  title: string;
  lessons: number;

  constructor(title: string, lessons: number) {
    this.title = title;
    this.lessons = lessons;
  }

  summary(): string {
    return `${this.title} has ${this.lessons} lessons`;
  }
}

const course = new Course("TypeScript", 15);
console.log(course.summary());

Constructor Initialization

With strict class checking enabled, TypeScript expects class properties to be initialized before they are used. You can initialize properties where they are declared, assign them in the constructor, or make them optional when they may be missing.

This catches a common JavaScript bug: creating an object where a method assumes a property exists, but the constructor never assigned it.

Property Initialization
class UserSession {
  userId: number;
  createdAt: Date = new Date();
  lastSeenAt?: Date;

  constructor(userId: number) {
    this.userId = userId;
  }

  touch(): void {
    this.lastSeenAt = new Date();
  }
}

Parameter Properties

Parameter properties are a TypeScript shortcut. Adding public, private, protected, or readonly to a constructor parameter automatically creates and assigns a class property.

This pattern removes boilerplate, but it should still be readable. For complex initialization, regular constructor code may be clearer.

Constructor Shortcut
class UserService {
  constructor(
    private readonly apiUrl: string,
    public readonly cacheEnabled: boolean
  ) {}

  endpoint(path: string): string {
    return `${this.apiUrl}/${path}`;
  }
}

const service = new UserService("https://api.example.com", true);
console.log(service.cacheEnabled);

Access Modifiers

Access modifiers describe where a class member can be used. public members are available everywhere. private members are available only inside the class. protected members are available inside the class and subclasses.

Use visibility to protect internal implementation details. A smaller public surface is easier to maintain because fewer outside files depend on class internals.

ModifierWhere It Can Be Used
publicAnywhere
privateInside the declaring class only
protectedInside the class and subclasses
readonlyAssigned once, then not reassigned
Private State
class Counter {
  private value = 0;

  increment(): void {
    this.value++;
  }

  current(): number {
    return this.value;
  }
}

const counter = new Counter();
counter.increment();
console.log(counter.current());

Getters and Setters

Getters and setters let a class expose property-like access while still running logic. A getter computes or returns a value. A setter validates or transforms input before storing it.

Use them when property syntax improves readability. If an operation is expensive, asynchronous, or has side effects, a normal method is usually clearer.

Getter and Setter
class Product {
  constructor(
    public readonly name: string,
    private amount: number
  ) {}

  get price(): number {
    return this.amount;
  }

  set price(value: number) {
    if (value < 0) {
      throw new Error("price cannot be negative");
    }
    this.amount = value;
  }
}

Implementing Interfaces

A class can implement an interface to promise that it provides a required shape. This is useful when different classes should be interchangeable through the same contract.

The interface checks the public side of the class. Private implementation details can change as long as the class still satisfies the public contract.

implements Interface
interface Repository<T> {
  findById(id: number): T | null;
}

type User = { id: number; name: string };

class UserRepository implements Repository<User> {
  private users: User[] = [{ id: 1, name: "Admin" }];

  findById(id: number): User | null {
    return this.users.find(user => user.id === id) ?? null;
  }
}

Inheritance and protected Members

Classes can extend other classes. The subclass inherits public and protected members and can override methods. Use inheritance only when the relationship is genuinely "is a" and shared behavior belongs in a base class.

Deep inheritance trees are hard to maintain. Prefer composition when a class simply needs to use another object to do part of its work.

Extending a Base Class
class BaseService {
  protected log(message: string): void {
    console.log(`[service] ${message}`);
  }
}

class BlogService extends BaseService {
  publish(title: string): void {
    this.log(`published ${title}`);
  }
}

Static Members

Static members belong to the class itself, not to an instance. They are useful for factories, constants, and helper methods that are closely related to the class concept.

Avoid using static members as a place for hidden global state. Hidden shared state can make tests and application behavior harder to reason about.

Static Factory
class ApiUrl {
  static readonly defaultBase = "https://api.example.com";

  static join(path: string): string {
    return `${ApiUrl.defaultBase}/${path.replace(/^\\//, "")}`;
  }
}

console.log(ApiUrl.join("/users"));

When to Use Classes

Do not use classes only because they are available. Many TypeScript programs are cleanly written with functions, plain objects, and modules. Use classes when identity, lifecycle, inheritance, dependency injection, or method-based behavior makes the design clearer.

For simple data containers, an interface or type alias is often enough. For stateful services or models with behavior, a class may be the right tool.

  • Use classes for services with dependencies and behavior.
  • Use interfaces or type aliases for plain data shapes.
  • Keep class public APIs small and intentional.
  • Avoid deep inheritance trees when composition would be clearer.
  • Prefer methods for actions and getters for cheap derived values.
Key Takeaways
  • TypeScript classes add type checking to JavaScript classes.
  • Class properties should be initialized or marked optional when strict checking is enabled.
  • Parameter properties reduce constructor boilerplate.
  • Access modifiers help protect implementation details.
  • Classes can implement interfaces for public contracts.
  • Inheritance is available, but composition is often simpler.
  • Use classes when behavior and lifecycle belong with the data.

Ready to Level Up Your Skills?

Explore 500+ free tutorials across 20+ languages and frameworks.