Link

Mixins

by @julianrubisch julianrubisch

Bad

// overlay_controller.js
import { Controller } from "stimulus";

export default class extends Controller {
  showOverlay(e) {
    // ...
  }
  
  hideOverlay(e) {
    // ...
  }
}
// dropdown_controller.js
import OverlayController from "./overlay_controller";

export default class extends OverlayController {
  //...
}

// flyout_controller.js
import OverlayController from "./overlay_controller";

export default class extends OverlayController {
  //...
}

Good

// mixins/useOverlay.js
export const useOverlay = controller => {
  Object.assign(controller, {
    showOverlay(e) {
      // ...
    },
    hideOverlay(e) {
      // ...
    }
  });
};
// dropdown_controller.js
import { useOverlay } from "./mixins/useOverlay";

export default class extends OverlayController {
  connect() {
    useOverlay(this);
  }

  //...
}

// flyout_controller.js
import { useOverlay } from "./mixins/useOverlay";

export default class extends OverlayController {
  connect() {
    useOverlay(this);
  }

  //...
}

Rationale

Stimulus controllers are meant to be used as mixins themselves (i.e. applying multiple controllers to one DOM element, thus mixing in behavior). Sometimes though, it might even be advisable to share behavior on the controller level. Inheritance isn’t always the answer to this - more often than not, it leads to architectural issues down the road, e.g. when you discover that you need to inherit behavior from two different parents - which is impossible in JavaScript. Often, it’s preferable to mix in behavior that can be used across controllers with a simple trick.

If you’re unsure about whether inheritance or mixin is the correct pattern, ask yourself:

  • does my controller have a is a relation to the target => use inheritance
  • does my controller have a acts as a relation to the target => use mixins

Counterindications

Mixins might still not be the right choice, composition might suit your needs even better. Is what you’re modelling a trait (acts as a), or a collaborator (has a)? Use composition, i.e. model your collaborator as a separate JS module or class and instantiate it in the connect method!

References

Codesandbox Example