[Fixed]-Passing values to constructor of custom HTMLElement

10👍

Remember that none of the regular HTML elements come with constructor arguments either: don’t rely on them. Instead, rely on attributes, with JS property backing, and hook those up according to the custom elements spec.

So, in the same way that you’d have to the following code for an Image:

let img = new Image();
img.src = "https://example.com/someimage.jpg";
img.width = ...
...

or a script element:

let script = document.createElement("script");
script.src = ...
// or script.textContent = ...

your component should not need constructor properties. You’d use it like this:

let preview = new TrackingPreview();
preview.width = 1920;
preview.height = 1080;

or in HTML form,

<track-preview width="100" height="200" ...></track-preview>

And have your custom element react to those properties getting set:

class TrackingPreview extends HTMLElement {

  static get observedAttributes() {
    return [`width`, `height`];
  }
  
  constructor() {
    super();
    const shadow = this.attachShadow({mode: 'open'});
    const video = this.video = document.createElement('video');
    video.setAttribute(`controls`, `controls`);
    video.setAttribute(`width`, this.getAttribute(`width`) || 1920);
    video.setAttribute(`height`, this.getAttribute(`height`) || 1080);
    shadow.appendChild(video);
  }

  get width() {
    return this.video.width;
  }

  set width(val) {
    this.setAttribute(`width`, val);
  }

  get height() {
    return this.video.height;
  }

  set height(val) {
    this.setAttribuite(`height`, val);
  }

  attributeChangedCallback(name, oldValue, newValue) {
    if (name === `width`) {
      this.video.width = newValue;
    }
    if (name === `height`) {
      this.video.height = newValue;
    }
    // ...
  }

  // ...
}

7👍

The rules of a constructor for a Web Component are pretty strict. One rule is that you are not allowed to pass in any arguments into the constructor.

https://w3c.github.io/webcomponents/spec/custom/#custom-element-conformance

Instead use both a property and an attribute

class TrackingPreview extends HTMLElement {
  static get observedAttributes() {
    return ['videoid'];
  }

  constructor() {
    super();

    const video = document.createElement('video');
    video.setAttribute('controls', '');
    video.width = 192;
    video.height = 108;
    this.video = video;
    this.attachShadow({mode: 'open'}).appendChild(video);
  }

  attributeChangedCallback(attrName, oldVal, newVal) {  
    if (oldVal !== newVal) {
      // Since we only watch videoid there is no need to check attrName
      this.video.setAttribute('id', newVal);
    }
  }
  
  get videoId() {
    return this.video.getAttribute('id');
  }

  set videoId(val) {
    if (val == null) { // check for null and undefined
      this.removeAttribute('videoid');
    }
    else {
      this.setAttribute('videoid', val);
    }
  }
}

customElements.define('tracking-preview', TrackingPreview);

setTimeout(function() {
  let el = document.querySelector('tracking-preview');
  if (el) {
    console.log(el.videoId);
    el.videoId = 99;
    console.log(el.videoId);
  }
}, 500);
<tracking-preview videoid="10"></tracking-preview>

When you attribute videoid is set then the attributeChangedCallback function is called and you pass the value down to your video element.

In my timeout function I both read and write the videoId property which read and writes the videoid attribute.

-3👍

You can do it with the new keyword:

const tacking_preview = new TrackingPreview( id )

Leave a comment