⚡IntermediateClasses & Decorators
Advanced Classes & Mixins
Go beyond basic classes with abstract classes, static blocks, private fields, and the mixin pattern for reusable, composable behaviors.
Overview
TypeScript classes have several advanced features that enable better architecture. **Abstract classes** define a template: they may contain concrete methods and abstract method signatures that subclasses must implement. They are the right tool when you have shared logic alongside required extension points.
**Private class fields** (`#field`) are a JavaScript stage-4 feature that TypeScript supports - they are truly private at runtime (unlike the `private` keyword which is TypeScript-only and erased at compile time). This distinction matters when your code is inspected or called from plain JavaScript.
**Mixins** are a pattern for composable, reusable behaviors without multiple inheritance. A mixin is a function that takes a base class and returns a new class extending it. You can apply multiple mixins to compose complex behavior from small, single-purpose pieces.
Key Points
- •Abstract class: `abstract class Shape` - cannot be instantiated directly; provides partial implementation
- •Abstract method: `abstract draw(): void` - no body; each subclass must implement it
- •JavaScript private fields: `#field` - truly private at runtime; not visible even via reflection
- •`private` keyword: TypeScript-only access control; erased to public at runtime; only TS catches violations
- •Static blocks: `static { ... }` inside a class body run once when the class is initialized
- •Mixin pattern: `type Constructor<T = {}> = new (...args: any[]) => T` - the reusable base for mixins
- •Applying mixins: `class MyClass extends Timestamped(Activatable(Base)) {}`
Examples
Abstract classes
abstract class Shape {
constructor(public color: string) {}
// Concrete method - shared by all subclasses
describe(): string {
return `A ${this.color} ${this.constructor.name}`;
}
// Abstract - each subclass must implement this
abstract area(): number;
abstract perimeter(): number;
}
class Circle extends Shape {
constructor(color: string, public radius: number) {
super(color);
}
area(): number { return Math.PI * this.radius ** 2; }
perimeter(): number { return 2 * Math.PI * this.radius; }
}
class Rectangle extends Shape {
constructor(color: string, public width: number, public height: number) {
super(color);
}
area(): number { return this.width * this.height; }
perimeter(): number { return 2 * (this.width + this.height); }
}
// const s = new Shape('red'); // Error: cannot instantiate abstract class
const shapes: Shape[] = [new Circle('blue', 5), new Rectangle('red', 4, 6)];
for (const shape of shapes) {
console.log(shape.describe(), '| area:', shape.area().toFixed(2));
}Private fields vs private keyword
class Counter {
// TypeScript 'private' - erased at runtime, accessible from JS
private tsPrivate = 0;
// JavaScript '#' private - truly private, enforced at runtime
#jsPrivate = 0;
increment(): void {
this.tsPrivate++;
this.#jsPrivate++;
}
getValues(): [number, number] {
return [this.tsPrivate, this.#jsPrivate];
}
}
const c = new Counter();
c.increment();
c.increment();
console.log(c.getValues()); // [2, 2]
// TypeScript catches both access violations at compile time:
// c.tsPrivate; // Error in TS
// c.#jsPrivate; // Error in TS
// But at RUNTIME in plain JavaScript:
// (c as any).tsPrivate // works (it's just a property)
// c.#jsPrivate // ReferenceError: truly privateMixin pattern - composable behaviors
// The base constructor type for mixins
type Constructor<T = {}> = new (...args: any[]) => T;
// Timestamped mixin - adds createdAt and updatedAt
function Timestamped<TBase extends Constructor>(Base: TBase) {
return class extends Base {
createdAt = new Date();
updatedAt = new Date();
touch(): void {
this.updatedAt = new Date();
}
};
}
// Activatable mixin - adds isActive toggle
function Activatable<TBase extends Constructor>(Base: TBase) {
return class extends Base {
isActive = false;
activate(): void { this.isActive = true; }
deactivate(): void { this.isActive = false; }
};
}
// Base entity class
class Entity {
constructor(public id: number) {}
}
// Compose mixins - order: innermost applied first
class User extends Timestamped(Activatable(Entity)) {
constructor(id: number, public name: string) {
super(id);
}
}
const user = new User(1, 'Alice');
user.activate();
user.touch();
console.log(user.name); // "Alice"
console.log(user.isActive); // true
console.log(user.createdAt); // Date
console.log(user.updatedAt); // Date (after touch)Common Mistakes
- Assuming TypeScript's `private` keyword is truly private - it's erased to a regular property at runtime; use `#` fields for true encapsulation
- Calling `super()` incorrectly in mixins - each mixin returns a class that extends the base, so `super(...args)` must forward all constructor arguments
- Trying to instantiate an abstract class directly - `new AbstractClass()` is a compile error; always instantiate the concrete subclass
Note
Choose `abstract class` over a plain `interface` when you have shared implementation to provide alongside the extension contract. Choose `interface` when you only have the contract and no shared code. Mixins fill the gap between them - reusable behavior blocks without forcing a deep inheritance chain.
Exercises
Write TypeScript code to solve each exercise. Click Run to compile and execute it right here, then mark as done.
Loading editor…
Completed: 0 / 3 exercises
Knowledge Check
Question 1 of 4