Search code examples
typescriptforeachchildrenlit-element

LitElement - ForEach children in template


I want to create <slot>s for each child, so for example here I have a menu and I want each child to be placed inside a <div> with item class.
I have created a little utility function to map the children:

export function ChildrenMap(el: LitElement, map: (el: HTMLElement, index: number) => TemplateResult): TemplateResult[] {
    console.log(el.children.length) // Returns 0 in Google Chrome and I guess Edge too.
    return (Array.prototype.slice.call(el.children) as HTMLElement[])
                .map(map);
}

And then I am using it in the render function:

render() {
    return html`
        <nav>
        ${ChildrenMap(this, (ele, index) => {
            let id = ele.id ? ele.id :  `item-${index}`;
            ele.setAttribute('slot', id);

            let klass = 'item';
            if(this.selected && this.selected == index) {
                klass += " selected";
            }

            return html`
                <div class="${klass}" data-index="${index}">
                <slot name="${id}"></slot>
                </div>`;
        })}
        </nav>
    `;
}

This works fine in FireFox, but as my comment above says, in Google Chrome, the element has 0 children at the point of render, so the <nav> is empty.
Can anybody explain why the element has 0 children at the point of render? Perhaps I am going about this the wrong way, does anybody have any alternatives to do this?

Many thanks


Solution

  • Update: Mozilla/FireFox fixed this bug early 2021

    Actually FireFox is wrong at this point, it invokes the connectedCallback too late

    Bug confirmed by Mozilla engineer Anne van Kesteren:

    For details see StackOverflow thread:

    There are no DOM children to read yet in the connectedCallback because all DOM children are still being parsed. (it could potentially be a very large DOM structure)

    The work-around (like you found) is to wait:

    connectedCallback(){
      setTimeout(()=>{
        // now you can access this. DOM children
      },0);
    }
    

    All suggestions with Promises etc. effectively do the same: Wait till the Event Loop is empty