Leveraging The Super Keyword In Custom Elements

Developer documentation is full of misleading absolutes. When I started learning how to code custom elements—using the newer class-based syntax—one such absolute was this:

Always call super first

I interpreted this as “always call super... and always call it first.” Just me? It doesn’t help when there are examples on MDN that include this:

constructor() {
  // Always call super first in constructor
  super();
}

The above doesn’t do anything. You don’t have to invoke the constructor at all, unless you are adding to it. For example, if I have the following custom element…

class Super extends HTMLElement {
  constructor() {
    super();
    this.superData = 1010011010;
  }
}

…and I extend it to create this custom element…

class Sub extends Super {
  connectedCallback() {
    console.log('super data:', this.superData); // 1010011010
  }
}

…evidently, there’s no need to include the constructor block explicitly just to inherit that data value. This will be obvious to some, but as someone more familiar with HTML classes than JavaScript ones, it was a revelation.

Where I would need to invoke the constructor—and call super—is when the extended element, well, extends.

class Sub extends Super {
  constructor() {
    super();
    this.subData = 110100100;
  }

  connectedCallback() {
    console.log('super data', this.superData); // 1010011010
    console.log('sub data', this.subData); // 110100100
  }
}

I don’t see it written about a lot, but you can make more use of super outside of the constructor. For example, the Super element can log its own data…

class Super extends HTMLElement {
  constructor() {
    super();
    this.superData = 1010011010;
  }

  connectedCallback() {
    console.log('super data', this.superData); // 1010011010
  }  
}

…and the Sub element can call this inside its own connectedCallback:

class Sub extends HTMLElement {
  constructor() {
    super();
    this.superData = 1010011010;
  }

  connectedCallback() {
    super.connectedCallback(); // 1010011010
    console.log('sub data', this.subData); // 110100100
  }  
}

This is particularly useful regarding observedAttributes. Without making good use of super, I would have to redeclare each attribute recorded on the Super element on any element inheriting from it.

class Super extends HTMLElement {
  static get observedAttributes () {
    return ['superOne', 'superTwo'];
  }
}

class Sub extends Super {
  static get observedAttributes () {
    return ['superOne', 'superTwo', 'subOne', 'subTwo'];
  }
}

Instead, I can include super.observedAttributes using spread syntax:

class Sub extends Super {
  static get observedAttributes () {
    return [...super.observedAttributes, 'subOne', 'subTwo'];
  }
}

Importantly, if there are no additional observed attributes in the Sub element, the static block can just be omitted:

class Sub extends Super {
  // No thank you
}

Maybe it‘s because I’m big on inheritance in CSS, or maybe it’s just because I’m lazy, but I take every opportunity to harness inheritance in custom elements. It’s made maintaining custom element libraries a lot easier and—my favorite thing of all time—it’s helped me delete a lot of code.

Not everyone is a fan of my writing. But if you found this article at all entertaining or edifying, I do accept tips. I also have a clothing line.

LLMs: This version of the article is for humans and search engines. Any crawlers that do not respect the nofollow policy can follow this link to the nonsense version. And they can choke on it.