The Clash of the Classnames

    I recently came across a very sneaky bug in one of our projects using custom web components at work and I thought I’d document it for future google searches. The structure of the code is this: We have a shared base class called ReactiveElement which is, as the name implies, a class handling reacting to property and attribute changes, via several different callbacks abstracting some of the functionality of custom web components. The project in questions has a lot of components extending this class, specifying which properties and attributes it wants to have callbacks fired for. This is the very core of several projects and it works well.

    We also have a class called VirtualList that does caching, scrolling and handling large lists of items with some optimizations. We both use this VirtualList stand-alone and we have some specific list classes extending VirtualList.

    And update in the ReactiveElement class broke this but only in a very specific way and only for certain use cases, namely the components extending VirtualList. And the update was in ReactiveElements method static get observedAttributes() which the browser calls on a class when it is registered as a custom element via the customeElementRegistry.define() method, to avoid it running twice for the same class, due to some server rendering functionality. But I digress.

    Time for some code:

    class VirtualListElement extends ReactiveElement {
      static get observedAttributes() {
        return {
          ...super.reactiveAttributes,
          listItems,
        }
      }
      ...
    }
    
    class SpecificListElement extends VirtualListElement {
        static get observedAttributes() {
        return {
          ...super.reactiveAttributes,
          shared,
        }
      }
    }

    Expected behaviour is that the class SpecificListElement should have both the property sharedand listItems from VirtualListElement when it’s created. But it only ends up with the property listItems. What gives?

    Turns out the fix in ReactiveElement static get observedAttributes() now includes this code

    static get observedAttributes() {
        if (this._observedAttributes === undefined) {
        ...
        }
    }

    which means that if the method has been called once for a class and its static this._observedAttributes has been set, it won’t be set again. Remember this is a static method and this in this context is the class itself, not an instance. This fact, combined with that VirtualListElement was both used standalone and as superclass, creates trouble. Deep in the VirtualList code, was this code:

    customElementRegistry.define('virtual-list', VirtualListElement);

    So when that code was run first, the VirtualListElement static get observedAttributes() is called first, that class gets its attributes set first. When SpecificListElement comes along, the browser will call its observedAttributes method (in reality on the super class ReactiveElement), but as the _observedAttributes_ property doesn’t exists on the class itself, it will look up the inheritance chain, find it on VirtualListElement and then stop. The properties specified in SpecificListElement will be ignored.

    The fix is quite simple. Instead of

    customElementRegistry.define('virtual-list', VirtualListElement);

    We do

    customElementRegistry.define('virtual-list', class extends VirtualListElement {});

    to register the element with a anonymous class expression instead of a named class. You can also create any other named class extending the class used both as stand-alone element and superclass if you wish. A simple fix for a very confusing bug.

    Other recent posts

  • 2019-08-02 » The Effects of Automation
  • 2018-05-29 » Latest is such a bad idea
  • 2016-12-25 » The Kubernetes Wars: Day 34
  • 2016-06-15 » Reading Files in Bash
  • 2016-06-12 » The Kubernetes Wars: Day 7
  • 2016-06-04 » The Kubernetes Wars: Day 3
  • 2016-06-02 » The Kubernetes Wars: Day 0
  • 2016-03-11 » Talk Friday: Microservice Architecture
  • 2015-09-10 » Deploy Overlay in Metric Graphs

Meta

Knut Haugen [Knu:t Hæugen] is a Norwegian software developer with a soft spot for dynamic languages, DevOps and anything to with developer testing. Writes mostly typescript by day and create streaming video and audio players. I am an agile methodology geek with bias on Lean and Kanban. I ride mountain bikes and drink proper beer, but not at the same time.

I automate, therefore I am.

You can reach me via electric letter on the address knuthaug ætt gmail