Stencil: Slots fails to distribute dynamic content

Created on 5 Oct 2017  路  3Comments  路  Source: ionic-team/stencil

Resources:
Before submitting an issue, please consult our docs.

Stencil version:

 @stencil/[email protected]

I'm submitting a ...

[x] bug report
[ ] feature request

Current behavior:

When applying dynamic lightdom content (later than the initial render cycle), the current slot implementation doesn't distribute correctly, even throw errors when applied.

I tried to make tests to reproduce using jest but it doesn't seem that it's possible at the moment, even with static content (though this actually works when rendering in a browser).

  it('should distribute slots correctly', async () => {
    const element = await render({
      components: [MyElement],
      html: '<my-element>slotted</my-element>'
    });

    const expected = `<div><div>top</div>slotted<div>bottom</div></div>`;

    await flush(element);

    expect(element.innerHTML).toEqual(expected);
  });

  /*
    Expected value to equal:
      "<div><div>top</div>slotted<div>bottom</div></div>"
    Received:
      "slotted<div><div>top</div><div>button</div></div>"
  */

Expected behavior:

Light dom in slots to be distributed correctly

Steps to reproduce:

see related code

Related code:

import { Component } from '@stencil/core';

@Component({
  tag: 'my-element'
})
export class MyElement {

  render() {
    return (
      <div>
        <div>top</div>
        <slot></slot>
        <div>bottom</div>
      </div>
    );
  }
}

Example 1

Static content composition is fine.

<!-- implementation -->
<my-element>slotted</my-element>

<!-- resulting composed dom -->
<my-element>
  <div>
    <div>top</div>
    slotted
    <div>bottom</div>
  </div>
<my-element>

Example 2 _(defering 90ms)_

Slightly defering the dom manipulation composes correctly, I guess due to async rendering.

<!-- implementation -->
<my-element>original</my-element>

<script>
  const root = document.querySelector('my-element');
  const dynamicElement = document.createElement('div');
  dynamicElement.textContent = 'dynamic';

  setTimeout(() => {
    root.appendChild(dynamicElement);
  }, 90);
</script>

<!-- resulting composed dom -->
<my-element>
  <div>
    <div>top</div>
    original
    <div>dynamic</div>
    <div>bottom</div>
  </div>
<my-element>

Example 3 _(defering 102ms)_

Once we defer lightdom manipulation a little more things starts to get wacky.

<!-- implementation -->
<my-element>original</my-element>

<script>
  const root = document.querySelector('my-element');
  const dynamicElement = document.createElement('div');
  dynamicElement.textContent = 'dynamic';

  setTimeout(() => {
    root.appendChild(dynamicElement);
  }, 102);
</script>

<!-- resulting composed dom -->
<my-element>
  <div>dynamic</div>
  <div>
    <div>top</div>
    <!---->
    static
    <div>bottom</div>
  </div>
<my-element>

Example 4 _(defering 150ms+)_

Defering more than 105ms (at least on my machine) produces an error:

Uncaught DOMException: Failed to execute 'insertBefore' on 'Node': The new child element contains the parent.

$insertBefore: function insertBefore(parentNode, childNode, referenceNode) {
    parentNode.insertBefore(childNode, referenceNode);
}

referenceNode is null at this time.

<!-- implementation -->
<my-element>original</my-element>

<script>
  const root = document.querySelector('my-element');
  const dynamicElement = document.createElement('div');
  dynamicElement.textContent = 'dynamic';

  setTimeout(() => {
    root.appendChild(dynamicElement);
  }, 150);
</script>

<!-- resulting composed dom -->
<my-element>
  <div>dynamic</div>
  <div>
    <div>top</div>
    <!---->
    static
    <div>bottom</div>
  </div>
<my-element>

Other information:

None at this time

Most helpful comment

Ah ok, well shadowDOM should fix that issue, I'm happy to hear

All 3 comments

This is a current limitation of the slot system, and without investing in a larger codebase and introducing new bugs I think we'll have to close this for now. However, we will be shipping shadow DOM as the default soon so you'll be able to use that instead. Thanks for the bug report!

Ah ok, well shadowDOM should fix that issue, I'm happy to hear

@adamdbradley how would you test the content of a slot in jest?

e.g.

given an element in a before each callback as such

    beforeEach(async () => {
      element = await render({
        components: [Grid],
        html: '<grid></grid>',
      });
    });

how would I set the content between the grid items?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

romulocintra picture romulocintra  路  3Comments

joewoodhouse picture joewoodhouse  路  3Comments

ryanmunger picture ryanmunger  路  3Comments

elmariofredo picture elmariofredo  路  3Comments

kensodemann picture kensodemann  路  3Comments