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
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
{
"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
{
"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
- The constructor parameter property stores logger on the instance.
- The private array cannot be accessed directly outside the class.
- The getter exposes a computed value without exposing the mutable array.
Expected result
Added: Learn classes
14. 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.
