← 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.
Syntax
typescript
function MixinName<TBase extends Constructor<Required>>(Base: TBase) {
  return class extends Base {
    // can use members of Required
  };
}

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());
Related Topics
{# common_mistakes/chapter_summary/browser_support: on Hindi pages the view already swaps in the hi_ translation fields (or blanks these out if untranslated), so this renders correctly for both languages without a lang_code check here. #}
Common Mistakes
  1. Using a mixin on a base class that lacks the required property, such as name, which fails to compile.
  2. Constraining with Constructor only and then using this.name inside the mixin, which is an error.
  3. Writing TBase extends HasName instead of TBase extends Constructor<HasName>, since the constraint should describe a class, not an instance.
🔒

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.