Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Differences between ways to dynamically instantiate a web component

There are several ways a web component (autonomous custom elements only with regard to this question) can "come to life".

Are there notable differences between the three options below?

Option 1:

const foo = document.createElement('foo-element');
document.body.appendChild(foo);

Option 2:

const div = document.createElement('div');
div.innerHTML = '<foo-element></foo-element>'
const foo = div.firstElementChild;
document.body.appendChild(foo);

Option 3:

const foo = new FooElement;
document.body.appendChild(foo);

I wrote some Unit Tests based on a Karma/Mocha stack, and created my instances with option 3.

Is this sufficient, that means, can I rely on the components having identical state/behaviour using either method, or is it necessary to repeat all my tests with all different options of instantiating applied?

One of my web components fails to be instantiated using document.createElement because of an error:

VM977:1 Uncaught DOMException: Failed to construct 'CustomElement':
The result must not have attributes
at <anonymous>:1:10

The fact that the same component can be instantiated with no problems using new tells me that, behind the curtain, there must be notable differences especially between new FooElement and document.createElement('foo-element').

I can write three general tests to test all three ways of instantiating, for sure, but is this enough?

Or should all of my existing tests be run with all 3 options of instantiating?

Or asked differently:

Is every instance exactly the same after instantiating? (assuming there is no error)

like image 898
Nils Avatar asked Aug 25 '20 12:08

Nils


1 Answers

The difference in the 3 methods is manifested if you register foo-element as a custom HTML element with the CustomElementRegistry.define() method. Based on my experimentation, the second method cannot take advantage of any special processing provided by registering the custom element. Also, the first method must be done as follows:

document.createElement("p", { is: "foo-element" });

where I have defined foo-element to extend the <p> tag as an example.

Anyway, an example can explain this better. In the code below I have defined FooElement to extend the <p> tag to automatically be initialized with the text "I am foo."

// Create a class for the element
class FooElement extends HTMLParagraphElement {
  constructor() {
    // Always call super first in constructor
    super();
    this.innerText = 'I am foo';
  }
}


// Define the new element (The CustomElementRegistry is available through the Window.customElements property):

customElements.define('foo-element', FooElement, { extends: 'p' });

Now execute the following snippet:

window.onload = function() {
    class FooElement extends HTMLParagraphElement {
      constructor() {
        // Always call super first in constructor
        super();
        this.innerText = 'I am foo';
      }
    }

    customElements.define('foo-element', FooElement, { extends: 'p' });

    const div1 = document.createElement('div');
    document.body.appendChild(div1);
    const foo1 = document.createElement("p", { is: "foo-element" });
    div1.appendChild(foo1);

    const div2 = document.createElement('div');
    document.body.appendChild(div2);
    div2.innerHTML = '<foo-element></foo-element>';

    const div3 = document.createElement('div');
    document.body.appendChild(div3);
    const foo3 = new FooElement();
    div3.appendChild(foo3);

};
<body>
</body>

We have created all three elements but only the thirst and third options have any effect as far as achieving the desired special processing. If you were to inspect the text you would see that the actual enclosing elements are in fact <p> tags.

As far as your DOMException is concerned, the first two methods that you showed should not cause an exception whether you have registered your element or not. However, the third method will throw an exception if FooElement is not a legitimate node such as is created by extending HTMLParagraphElement as in the above example. So, I would need more information on the exact circumstances of your exception.

Update

Here class FooElement does not inherit from a standard element and an exception is thrown:

window.onload = function() {
    class FooElement {
      constructor() {
      }
    }

    const div3 = document.createElement('div');
    document.body.appendChild(div3);
    const foo3 = new FooElement();
    div3.appendChild(foo3);

};
<body>
</body>
like image 118
Booboo Avatar answered Oct 16 '22 12:10

Booboo