Reputation: 342
I am having some trouble with the lifecycle methods in web components.
We want to dynamically order child elements being passed in as slots.
To illustrate, this web component takes a prop, iconPos
, and will determine whether the icon will be placed at the start or end of the slot.
<my-component iconPos="start">
<img src="/path/icon.svg" />
<div>{this.list}</div>
</my-component>
I haven't had any luck getting it working with ref
:
dc6b89e7.js:2926 TypeError: Cannot read properties of undefined (reading 'prepend')
Here's what I have so far:
@State() slotElement!: HTMLDivElement;
@Prop() iconPos: 'start' | 'end';
...
private createSlots() {
switch (this.iconPos) {
case 'start':
this.slotElement.prepend(<img />);
break;
case 'end':
this.slotElement.append(<img />);
break;
default:
throw new Error(
`Invalid value \`${this.iconPos}\`, passed into \`iconPos\`. Expected valid values are \`start\`, \`end\``.
);
}
}
render() {
return (
// iconPos="start"
<parent-component>
<div ref={(el) => (this.slotElement= el as HTMLDivElement)}>
<slot></slot>
</div>
</parent-component>
)
}
I would prefer to not use a CSS solution if possible. Any help would be much appreciated!
Upvotes: 0
Views: 1247
Reputation: 21143
Slotted content is NOT MOVED to <slot>
elements; it is reflected!!
So all styling and element operations must be done in "lightDOM"
For (very) long read see: ::slotted CSS selector for nested children in shadowDOM slot
That means you have to append
your elements in ligtDOM with:
this.append(this.firstElementChild)
You can't read the <my-component>
innerHTML before it is parsed; so you need to wait till the innerHTML elements are created. Thus you will see the DOM change.
A better method might be to not use <slot>
and declare your icon and content as attributes, and have the Web Component create the HTML.
<style>
span::after { content: attr(id) }
#FOO { background: lightgreen }
</style>
<my-component>
<span id="FOO"></span>
<span id="BAR"></span>
</my-component>
<my-component reversed>
<span id="FOO"></span>
<span id="BAR"></span>
</my-component>
<script>
window.customElements.define('my-component', class extends HTMLElement {
constructor() {
super().attachShadow({mode:'open'})
.innerHTML = `<style>::slotted(span){background:gold}</style>
${this.nodeName}<slot></slot><br>`;
}
connectedCallback() {
setTimeout(() => { // make sure innerHTML is parsed!
if (this.hasAttribute("reversed")) {
this.append(this.firstElementChild);
}
})
}
});
</script>
Upvotes: 1