Skip to content

APPLICATION DEVELOPMENT · LESSON 15 OF 24

Classes, interfaces and composition

Model stateful behavior and inject dependencies through a contract.

  • class
  • implements
  • #private
  • composition

Your runnable checkpoint

Extract into a new practice folder. Each download includes complete source, package.json, tsconfig.json and an offline lesson. Use Node.js 22 or 24. Each checkpoint is independent.

Install with npm install. Run npm run check, then npm run build. Terminal lessons use npm start; browser lessons use npm run serve and http://127.0.0.1:4173.

1. Understand the concepts

A class combines state with behavior. implements checks that instances have the interface members; it does not copy an implementation from the interface. Initialize required fields in the constructor or at their declarations.

JavaScript #private fields enforce runtime privacy. TypeScript private is primarily a compile-time access restriction. readonly allows assignment during initialization but prevents later assignment through that type.

Composition passes a collaborating object into a class. TaskService can use any Logger with a log method, making it easy to replace console logging in a test. Inheritance is useful for genuine specialization, but is not required for sharing every behavior.

Abstract classes can provide shared implementation while requiring subclasses to implement abstract methods. protected allows subclass access. Use override with a base-class method and enable noImplicitOverride in larger projects to make overrides explicit.

2. Build and run

These are complete files. Preserve the paths shown below, including the src directory. The package and compiler configuration are included at the end.

src/main.ts

src/main.ts
interface Logger { log(message: string): void }
class ConsoleLogger implements Logger {
  log(message: string): void { console.log(message); }
}
class TaskService {
  #titles: string[] = [];
  constructor(private readonly logger: Logger) {}
  add(title: string): void {
    const clean = title.trim();
    if (!clean) throw new Error("Title is required");
    this.#titles.push(clean);
    this.logger.log(`Added: ${clean}`);
  }
  get count(): number { return this.#titles.length; }
}
const service = new TaskService(new ConsoleLogger());
service.add("Learn classes");
console.log(service.count);

package.json

package.json
{
  "name": "edupoly-typescript-classes-and-composition",
  "private": true,
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "check": "tsc --noEmit",
    "build": "tsc",
    "start": "node dist/main.js"
  },
  "devDependencies": {
    "typescript": "5.9.3"
  }
}

tsconfig.json

tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "lib": [
      "ES2022",
      "DOM"
    ],
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    "noEmitOnError": true,
    "verbatimModuleSyntax": true,
    "rootDir": "src",
    "outDir": "dist",
    "sourceMap": true,
    "types": [],
    "moduleDetection": "force"
  },
  "include": [
    "src/**/*"
  ]
}

3. Trace the code and check the result

  1. The constructor parameter property stores logger on the instance.
  2. The private array cannot be accessed directly outside the class.
  3. The getter exposes a computed value without exposing the mutable array.

Expected result

Added: Learn classes
1

4. Practice independently

Supply a logger that collects messages into an array.

Show the explanation

Create const messages: string[] = []; pass { log: message => { messages.push(message); } } to TaskService. Assert the collected message after adding a task.

When something goes wrong

If tsc is missing, run npm install in the folder containing package.json. If the output looks old, rebuild before running. Read the first compiler diagnostic and check the indicated field or argument. For module errors, preserve the .js extensions in imports. Browser examples must run through the included local server, not directly as TypeScript files.

Read the related official TypeScript documentation ↗