RizTech Academy logo
RizTech Academy
Classes, Modules and DecoratorsLesson 1 of 425 min

Classes and access modifiers

TypeScript classes are JavaScript classes with types added — plus a few genuinely useful features JavaScript lacks: typed properties, access modifiers (private, public), parameter properties, and more. Classes are less central to modern TypeScript than they once were (functions, objects, and unions do a lot of the work), but they matter — especially in Angular and NestJS, which are built on them. This lesson is the class, typed properly.

A typed class

class Member {
  name: string;
  age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }

  greet(): string {
    return `Namaste, ${this.name}`;
  }
}

const kavita = new Member("Kavita", 33);
kavita.greet();          // "Namaste, Kavita"
kavita.age.toFixed();    // fine — age is typed number
kavita.email;            // error: Property 'email' does not exist on type 'Member'

Properties are declared with types (name: string), the constructor is typed, and methods have typed parameters and returns. TypeScript checks every access — a property not on the class is an error. Under strict mode, you must initialise every declared property (in the constructor or with a default), or TypeScript flags it: an uninitialised name: string that could be undefined is a bug it catches.

Parameter properties — the boilerplate killer

The constructor above is repetitive: declare name, take name, assign this.name = name. TypeScript has a shortcut — parameter properties — that declares and assigns in one place:

class Member {
  constructor(
    public name: string,
    public age: number,
  ) {}
  // that's it — name and age are declared, typed, AND assigned automatically

  greet(): string {
    return `Namaste, ${this.name}`;
  }
}

Putting an access modifier (public, private, readonly) on a constructor parameter tells TypeScript "make this a property and assign it". The whole name: string; constructor(name) { this.name = name } dance collapses to constructor(public name: string). This is a genuinely nice feature — it removes real boilerplate — and it is used heavily in NestJS (where dependencies are injected as private parameter properties). Reach for it whenever a constructor just assigns its parameters to properties.

Access modifiers — public, private, protected

TypeScript adds access control that JavaScript historically lacked:

class BankAccount {
  public readonly id: string;      // visible everywhere, cannot be reassigned
  private balance: number;          // visible only inside this class
  protected owner: string;          // visible in this class and subclasses

  constructor(id: string, owner: string) {
    this.id = id;
    this.owner = owner;
    this.balance = 0;
  }

  deposit(amount: number): void {
    this.balance += amount;         // fine — inside the class
  }
}

const acc = new BankAccount("A1", "Kavita");
acc.deposit(100);        // fine — public method
acc.balance;             // error: Property 'balance' is private
acc.id = "A2";           // error: id is readonly
  • public (the default) — accessible everywhere.
  • private — accessible only within the class; acc.balance from outside is an error. Use it for internal state that outside code should not touch.
  • protected — accessible within the class and its subclasses, but not from outside.
  • readonly — combine with any of the above; the property can be set once (in the constructor) but never reassigned.

Encapsulation — hiding internal state behind private and exposing only what is meant to be used — is the point: it lets you change the internals without breaking callers, and prevents outside code from putting the object in a bad state. Make properties private by default and expose only what genuinely needs to be public.

One caveat: TypeScript's private is a compile-time check (erased at runtime). For true runtime privacy, JavaScript has native #private fields:

class Account {
  #balance = 0;            // truly private at runtime — inaccessible outside, even via JS
  deposit(n: number) { this.#balance += n; }
}

#balance is genuinely inaccessible outside the class at runtime, not just in TypeScript's view. Prefer #private when runtime privacy matters (a library's internals); TypeScript's private is fine for ordinary application code where compile-time checking is enough.

Getters, setters, and static members

Classes support computed accessors and class-level members:

class Circle {
  constructor(private radius: number) {}

  get area(): number {                    // a getter — accessed like a property
    return Math.PI * this.radius ** 2;
  }

  set diameter(value: number) {           // a setter — assigned like a property
    this.radius = value / 2;
  }

  static fromDiameter(d: number): Circle { // a static method — called on the class
    return new Circle(d / 2);
  }
}

const c = new Circle(10);
c.area;                    // 314.159...  — no parentheses; it is a getter
c.diameter = 20;           // calls the setter -> radius becomes 10
const c2 = Circle.fromDiameter(20);   // static factory method

A getter (get area()) is accessed like a property but computes its value — right for a derived value. A setter (set diameter()) runs code on assignment. Static members (static fromDiameter) belong to the class itself, not an instance — called as Circle.fromDiameter(...), and the idiomatic place for factory methods (like the factory pattern the design-patterns module covers).

When to use a class — and when not

Be honest, because beginners from Java over-use classes in TypeScript:

  • Use a class when you have data and behaviour bundled together with genuine internal state and encapsulation (a BankAccount, a Repository), when a framework requires it (Angular components, NestJS services), or when you need instances with identity.
  • Do not use a class for plain data — a type/interface and a plain object is lighter and more idiomatic (type User = { name: string }, not a User class with only fields). And do not use a class with a single method and no state — that is a function wearing a costume (the design-patterns lesson).

Much of what classes do in Java, TypeScript does with types, functions, and discriminated unions. Reach for a class when you genuinely need bundled state and behaviour or a framework demands it — not by reflex.

Check your work

How class properties are typed. Declared with types (name: string); strict mode requires every property be initialised.

What parameter properties do. An access modifier on a constructor parameter declares and assigns it in one place — killing the declare/assign boilerplate.

The three access modifiers. public (default, everywhere), private (this class only), protected (this class and subclasses); readonly combines with any.

What encapsulation buys. Hiding internal state behind private lets you change internals without breaking callers and prevents bad states.

private versus #private. TypeScript's private is compile-time (erased); #field is true runtime privacy — prefer # when runtime privacy matters.

Getters, setters, static. A getter computes a property-like value; a setter runs code on assignment; static members belong to the class (factory methods).

When to use a class, and when not. For bundled state+behaviour or framework requirements; not for plain data (use a type) or a single stateless method (use a function).

Practice

  1. Write a Member class with typed properties, a constructor, and a method. Access a non-existent property and read the error.
  2. Rewrite the constructor using parameter properties and confirm the behaviour is identical.
  3. Make a property private and try to access it from outside. Read the error. Make one readonly and try to reassign it.
  4. Convert a private field to a #private field and note it is inaccessible even to raw JavaScript.
  5. Add a getter and a setter and use them like properties.
  6. Add a static factory method and call it on the class.
  7. For three things you might model as a class, decide whether a class, a type+object, or a function is the right tool, and justify each.

Official documentation

Next: abstract classes and implementing interfaces.

Stuck on this lesson?

Being stuck is part of it — but being stuck alone for three days is not. Our internship programme pairs this curriculum with code review and one-to-one help from working developers, and it is free.

About the internship