Mixins

TypeScript (and JavaScript) classes support strict single inheritance. So you cannot do:

  1. class User extends Tagged, Timestamped { // ERROR : no multiple inheritance
  2. }

Another way of building up classes from reusable components is to build them by combining simpler partial classes called mixins.

The idea is simple, instead of a class A extending class B to get its functionality, function B takes class A and returns a new class with this added functionality. Function B is a mixin.

[A mixin is] a function that

  1. takes a constructor,
  2. creates a class that extends that constructor, with new functionality
  3. returns the new class

A complete example

  1. // Needed for all mixins
  2. type Constructor<T = {}> = new (...args: any[]) => T;
  3. ////////////////////
  4. // Example mixins
  5. ////////////////////
  6. // A mixin that adds a property
  7. function Timestamped<TBase extends Constructor>(Base: TBase) {
  8. return class extends Base {
  9. timestamp = Date.now();
  10. };
  11. }
  12. // a mixin that adds a property and methods
  13. function Activatable<TBase extends Constructor>(Base: TBase) {
  14. return class extends Base {
  15. isActivated = false;
  16. activate() {
  17. this.isActivated = true;
  18. }
  19. deactivate() {
  20. this.isActivated = false;
  21. }
  22. };
  23. }
  24. ////////////////////
  25. // Usage to compose classes
  26. ////////////////////
  27. // Simple class
  28. class User {
  29. name = '';
  30. }
  31. // User that is Timestampted
  32. const TimestampedUser = Timestamped(User);
  33. // User that is Timestamped and Activatable
  34. const TimestampedActivatableUser = Timestamped(Activatable(User));
  35. ////////////////////
  36. // Using the composed classes
  37. ////////////////////
  38. const timestampedUserExample = new TimestampedUser();
  39. console.log(timestampedUserExample.timestamp);
  40. const timestampedActivatableUserExample = new TimestampedActivatableUser();
  41. console.log(timestampedActivatableUserExample.timestamp);
  42. console.log(timestampedActivatableUserExample.isActivated);

Let’s decompose this example.

Take a constructor

Mixins take a class and extend it with new functionality. So we need to define what is a constructor. Easy as:

  1. // Needed for all mixins
  2. type Constructor<T = {}> = new (...args: any[]) => T;

Extend the class and return it

Pretty easy:

  1. // A mixin that adds a property
  2. function Timestamped<TBase extends Constructor>(Base: TBase) {
  3. return class extends Base {
  4. timestamp = Date.now();
  5. };
  6. }

And that is it ?