← Back to TypeScript Course | Chapter 22: Mixins | Lesson 3 of 4

Constrained Mixins

A constrained mixin requires the base class to provide particular properties or methods. Generic constraints let TypeScript verify that a mixin only receives compatible classes.

Why Constrain a Mixin?

A mixin sometimes needs a specific property or method already present on the base class before it can add anything useful — a structural constraint makes that requirement explicit and checkable by TypeScript.

Example: Why Constrain a Mixin?

typescript
interface HasName { name: string; }
type Constructor<T = {}> = new (...args: any[]) => T;
function Greetable<TBase extends Constructor<HasName>>(Base: TBase) {
  return class extends Base {
    greet() { return `Hello, ${this.name}`; }
  };
}
class Person { name = "Ravi"; }
class GreetablePerson extends Greetable(Person) {}
console.log(new GreetablePerson().greet());

Constraining with Interfaces

An interface can describe exactly what capability the base class must already have; the mixin's constructor-type parameter then combines that interface with a construct signature to enforce the requirement.

Example: Constraining with Interfaces

typescript
interface Timestamped { createdAt: Date; }
type Constructor<T = {}> = new (...args: any[]) => T;
function Auditable<TBase extends Constructor<Timestamped>>(Base: TBase) {
  return class extends Base {
    age() { return Date.now() - this.createdAt.getTime(); }
  };
}
class Record_ { createdAt = new Date(); }
class AuditableRecord extends Auditable(Record_) {}
console.log(typeof new AuditableRecord().age());

Constrained Method Requirements

A mixin can require a specific method instead of a field — the constraint ensures the base class actually exposes a callable member matching the signature the mixin depends on internally.

Example: Constrained Method Requirements

typescript
interface Serializable { serialize(): string; }
type Constructor<T = {}> = new (...args: any[]) => T;
function Loggable<TBase extends Constructor<Serializable>>(Base: TBase) {
  return class extends Base {
    log() { console.log(this.serialize()); }
  };
}
class Data {
  serialize() { return "data payload"; }
}
class LoggableData extends Loggable(Data) {}
new LoggableData().log();

Multiple Constraints

When a mixin depends on several capabilities at once, combine them into a single interface — the base class passed in must satisfy every member of that combined interface, not just some of them.

Example: Multiple Constraints

typescript
interface HasName { name: string; }
interface HasAge { age: number; }
type Constructor<T = {}> = new (...args: any[]) => T;
function Describable<TBase extends Constructor<HasName & HasAge>>(Base: TBase) {
  return class extends Base {
    describe() { return `${this.name}, age ${this.age}`; }
  };
}
class Person { name = "Ravi"; age = 25; }
class DescribablePerson extends Describable(Person) {}
console.log(new DescribablePerson().describe());

Benefits of Constrained Mixins

Constraints improve reliability because an incompatible base class gets rejected at compile time rather than failing mysteriously at runtime, and they double as documentation of exactly what the mixin expects from its input.

Example: Benefits of Constrained Mixins

typescript
interface HasId { id: number; }
type Constructor<T = {}> = new (...args: any[]) => T;
function Findable<TBase extends Constructor<HasId>>(Base: TBase) {
  return class extends Base {
    key() { return `record-${this.id}`; }
  };
}
class Entity { id = 1; }
class FindableEntity extends Findable(Entity) {}
console.log(new FindableEntity().key());
🔒

Chapter Quiz — Complete all 4 topics to unlock

0/4 topics done

Complete these topics first:

Login to run this code

C/C++/Java/PHP execution requires a free account. Your code is saved — you'll land right back in the editor after logging in.