2

Is there anyway to know how many children a named slot contains? In my Stencil component I have something like this in my render function:

<div class="content">
  <slot name="content"></slot>
</div>

What I want to do is style the div.content differently depending on how many children are inside the slot. If there are no children in the slot, then div.content's style.display='none', otherwise, I have a bunch of styles applied to div.content that make the children appear correctly on the screen.

I tried doing:

  const divEl = root.querySelector( 'div.content' );
  if( divEl instanceof HTMLElement ) {
    const slotEl = divEl.firstElementChild;
    const hasChildren = slotEl && slotEl.childElementCount > 0;
    if( !hasChildren ) {
      divEl.style.display = 'none';
    }
  }

however this is always reporting hasChildren = false even when I have items inserted into the slot.

Marek Krzeminski
  • 1,308
  • 3
  • 15
  • 40

2 Answers2

5

If you are querying the host element you will get all the slotted content inside of it. That means that the host element's children are going to be all the content that will be injected into the slot. For example try to use the following code to see it in action:

import {Component, Element, State} from '@stencil/core';

@Component({
  tag: 'my-component',
  styleUrl: 'my-component.css',
  shadow: true
})
export class MyComponent {
  @Element() host: HTMLElement;
  @State() childrenData: any = {};

  componentDidLoad() {
    let slotted = this.host.children;
    this.childrenData = { hasChildren: slotted && slotted.length > 0, numberOfChildren: slotted && slotted.length };
  }

  render() {
    return (
    <div class="content">
      <slot name="content"></slot>
      <div>
        Slot has children: {this.childrenData.hasChildren ? 'true' : 'false'}
      </div>
      <div>
        Number of children: {this.childrenData.numberOfChildren}
      </div>
    </div>);
  }
}
Gil Fink
  • 747
  • 9
  • 10
  • thank you Gil, I didn't expect to find all slotted content at the host element. Now that I know this, I can update my code to do what I need. – Marek Krzeminski Dec 17 '18 at 03:31
0

The accepted solution is actually not the correct way to do it. Even the code example is wrong. It is using a named slot name="content". Only elements with slot="content" attribute from the light DOM will be slotted into that slot; hence simply checking this.host.children is not sufficient at all.

Instead, you should work with the slotchange event (which also has the benefit of properly reflecting dynamic changes):

import {Component, Element, State} from '@stencil/core';

export type TSlotInfo = {
  hasSlottedElements?: boolean;
  numberOfSlottedElements?: number;
}

@Component({
  tag: 'my-component',
  shadow: true
})
export class MyComponent {
  @Element() host: HTMLElement;
  @State() slotInfo: TSlotInfo = {};

  handleSlotChange = (event: Event) => {
    let assignedElementCount = event.currentTarget.assignedElements().length;
    this.slotInfo = {
      hasSlottedElements: Boolean(assignedElementCount),
      numberOfSlottedElements: assignedElementCount,
    }
  }
  
  render() {
    return (
    <div class="content">
      <slot name="content" onslotchange={this.handleSlotChange}></slot>
      <div>
        Slot is populated: {this.slotInfo.hasSlottedElements}
      </div>
      <div>
        Number of slotted elements: {this.slotInfo.numberOfSlottedElements}
      </div>
    </div>);
  }
}
connexo
  • 53,704
  • 14
  • 91
  • 128