Skip to content
This repository has been archived by the owner on Jul 26, 2022. It is now read-only.

Content Assignment

Jan Miksovsky edited this page Jun 26, 2019 · 3 revisions

Checklist » Content

✓ Content Assignment

Can you place a <slot> element inside a component instance and have the component treat the assigned content as if it were directly inside the component?

Your component can find many more applications if the component itself can be incorporated into other components. This raises the prospect that your component will need to deal with assigned content. That term refers to DOM elements which may have been assigned to one component's <slot> element, and then further assigned a second (or third, etc.) time to another <slot> element, until they reach the final position in which they will appear.

Example

As a trivial example, suppose you have a component that can return the number of top-level children it's currently displaying. It has the following template:

<template>
  <slot></slot>
</template>

and a definition like:

class CountChildren extends HTMLElement {
  get count() {
    return this.children.length;
  }
})

You envision this component of your being used directly in HTML like so:

<body>
  <count-children>
    <div>Child one</div>
    <div>Child two</div>
    <div>Child three</div>
  </count-children>
</body>

When you get the count value of this particular <count-children> instance, it will return the value 3, because the instance has three children.

Now someone comes along who wants to develop a component that wraps your child-counting component. They use your component inside their component to count children. Their template looks like:

<template>
  <count-children id="counter">
    <slot></slot>
  </count-children>
</template>

Now they use it so:

<body>
  <wrapped-count-children>
    <div>Child one</div>
    <div>Child two</div>
    <div>Child three</div>
  </wrapped-count-children>
</body>

If you get the count value of this outer component, the answer is unexpected: it's always 1. That's because, in this situation, your wrapped-count-children component always contains a single element: the <slot> element inside the <count-children> instance.

To count-children, the value of its children property is an element list containing just the <slot> element. So the answer of 1 here is technically correct, but probably not expected. Rather, the intention here was probably to have count-children look inside the <slot> element and count what it finds there. And if any nodes assigned there are themselves <slot> elements, the count should recursively include nodes assigned to those slots.

Flattening content

What's really wanted is a way to flatten all slots to obtain the complete set of children the user sees. This can be done via the slot element's assignedNodes function, which supports an optional flatten parameter.

The template for count-children can stay the same:

<template>
  <slot></slot>
</template>

but to work as expected, the component definition asks the slot how many nodes have been assigned to it:

class CountChildren extends HTMLElement {
  get count() {
    const slot = this.shadowRoot.querySelector('slot');
    const nodes = slot.assignedNodes({ flatten: true });
    return nodes.length;
  }
})

With this, let's look again at the sample use shown earlier:

<body>
  <wrapped-count-children>
    <div>Child one</div>
    <div>Child two</div>
    <div>Child three</div>
  </wrapped-count-children>
</body>

The three <div> elements inside the <wrapped-count-children> instance will be assigned to that component's slot. Because that slot is sitting inside an instance of <count-children>, the three <div> elements will be assigned to the slot inside that element. When count-children is asked for its count, it will flatten the assigned nodes, and determine that there are 3 elements inside, as expected.

Clone this wiki locally