Skip to content

Mixins

Mixins are a standard JavaScript pattern for sharing behavior across Progressive Web Components. Since Elena’s Elena() factory is itself a mixin, custom mixins compose naturally with it.

Writing a mixin

A mixin is a function that takes a base class and returns an extended class:

js
const Draggable = (superClass) => class extends superClass {
  connectedCallback() {
    super.connectedCallback();
    this.setAttribute("draggable", "true");
    this.addEventListener("dragstart", this._onDragStart);
  }

  disconnectedCallback() {
    super.disconnectedCallback();
    this.removeEventListener("dragstart", this._onDragStart);
  }

  _onDragStart(e) {
    e.dataTransfer.setData("text/plain", this.text);
  }
};

Using a mixin

Apply a mixin by wrapping it around the Elena base class:

js
import { Elena, html } from "@elenajs/core";

class DraggableCard extends Draggable(Elena(HTMLElement)) {
  static tagName = "draggable-card";

  render() {
    return html`<div class="card">${this.text}</div>`;
  }
}
DraggableCard.define();

The mixin’s connectedCallback calls super.connectedCallback(), which reaches Elena’s built-in lifecycle. The result is a component with both Elena’s reactive rendering and your custom behavior.

Composition order

Always apply mixins after Elena():

js
class MyComponent extends Draggable(Elena(HTMLElement)) { }

This way the mixin’s super.connectedCallback() reaches Elena’s implementation and has access to its API.

Lifecycle rules

Elena’s lifecycle methods follow the standard super call pattern. Mixins that override lifecycle methods must call super to preserve Elena’s behavior:

MethodMust call super?
connectedCallback()Yes
disconnectedCallback()Yes
adoptedCallback()Yes
attributeChangedCallback()Yes
firstUpdated()Yes
updated()Yes
willUpdate()No (base is a no-op)

Multiple mixins

Mixins stack. Apply them left to right, with each wrapping the previous:

js
const Loggable = (superClass) => class extends superClass {
  connectedCallback() {
    super.connectedCallback();
    console.log(`${this.tagName} connected`);
  }
};
js
const Tooltipped = (superClass) => class extends superClass {
  connectedCallback() {
    super.connectedCallback();
    if (this.getAttribute("tooltip")) {
      this._setupTooltip();
    }
  }

  disconnectedCallback() {
    super.disconnectedCallback();
    this._teardownTooltip();
  }

  _setupTooltip() { /* ... */ }
  _teardownTooltip() { /* ... */ }
};
js
class FancyButton extends Loggable(Tooltipped(Elena(HTMLElement))) {
  static tagName = "fancy-button";
  // ...
}
FancyButton.define();

Adding props

Mixins can introduce their own props. List them in static props on the final component class so Elena can observe them:

js
const Sizeable = (superClass) => class extends superClass {
  /** @property @type {"sm" | "md" | "lg"} */
  size = "md";
};
js
class SizeableButton extends Sizeable(Elena(HTMLElement)) {
  static tagName = "sizeable-button";
  static props = ["size", "variant"];

  /** @property @type {"default" | "primary"} */
  variant = "default";

  render() {
    return html`<button>${this.text}</button>`;
  }
}
SizeableButton.define();

TIP

Props must always be listed in static props on the component class for Elena to observe and sync them. A mixin can provide the default value and logic, but the final class is responsible for registering the prop.