Reputation: 97
I have two components using Stencil (TypeScript), a parent and a child.
In the parent I accept props like textColor
, fontFamily
, etc. and use them in a function to call them in the return statement as a style. Also, I use another function to declare the props that need to be passed down.
What I'm now failing is to pass the function down to the child which is in a slot. Also I have the feeling that there is a smarter way to handle it with one instead of two functions.
Any help is highly appreciated.
Parent
import { Component, h, Prop } from '@stencil/core';
interface Styles {
[key: string]: string;
}
@Component({
tag: 'my-parent',
shadow: true,
})
export class MyParent {
@Prop() textColor: string;
@Prop() fontFamily: string;
getAccordionProps(): Styles {
return {
color: this.textColor
fontFamily: this.fontFamily
};
}
props(): Styles {
return {
fontColor: this.textColor,
fontWeight: this.fontWeight,
fontFamily: this.fontFamily,
};
}
render() {
return (
<Host>
<div class="some-content" style={this.getAccordionProps()}>
</div>
<slot {...this.props()}/>
</Host>
);
}
}
Child
import { Component, h, Prop, Method } from '@stencil/core';
@Component({
tag: 'my-child',
shadow: true,
})
export class MyChild {
@Prop() textColor: string;
@Prop() fontFamily: string;
render() {
return (
<Host style={this.getAccordionProps()>
{/* Use the <slot> element to insert the text */}
<slot />
</Host>
);
}
}
Upvotes: 2
Views: 2246
Reputation: 60
Parent
export class MyParent {
private children;
@Element() host: HTMLElement;
@Prop() prop1;
@Prop() prop2;
componentDidLoad() {
this.passPropsToChildren()
}
private passPropsToChildren = () => {
this.children = this.host.querySelectorAll('child');
this.children.forEach(child => {
child['prop1'] = this.prop1;
child['prop2'] = this.prop2;
});
}
render() {
return (
<Host>
<div class="some-content" style={this.getAccordionProps()}></div>
<slot />
</Host>
);
}
}
Child
export class Child {
@Prop() prop1;
@Prop() prop2;
render() {
return (
<Host style={this.getAccordionProps()>
{/* Use the <slot> element to insert the text */}
<slot />
</Host>
);
}
}
Upvotes: 0
Reputation: 17958
You may be misunderstanding what a slots are and how they work. A slot in a component is a place where any DOM can be added inside the light DOM of a component. A <slot>
element uses only one property/attribute - "name", so applying other properties to the slot element does nothing.
Slotted components and their parent components do not have any special access to each other - only standard DOM just like how a <span>
within a <div>
would have no special access to each other. So for example a child component does not inherit functions from its parent component. However, it can find its parent in the DOM and call the parent's functions. As in:
export class MyChild {
@Element() hostElement;
...
render() {
return (
<Host style={this.hostElement.parent.getAccordionProps()}>
<slot />
</Host>
);
}
}
The problem with this approach is that my-child is completely dependent on my-parent in order to work properly. It's better to keep separate components separate - don't assume a certain parent or child, instead design the components so they can be used independently. For example, to apply attributes to a slotted element like <my-child>
(not the <slot>
element itself), you would do that in the DOM not in the parent. For example:
<my-parent text-color="..." font-family="...">
<my-child font-color="..." font-family="...">
...
</my-child>
</my-parent>
If you want to apply properties to a slotted component from the parent, you need to find the component as an element and manipulate it as standard DOM. For example:
private myChildElement;
@Element() hostElement;
export class MyParent {
componentWillLoad() {
const child = this.hostElement.querySelector('my-child');
if (child) {
child.setAttribute('font-color', this.textColor);
...
}
}
...
}
Of course, the problem with this approach is that you may not know how the <my-parent>
component is being used - it could have none or more than one <my-child>
elements. This is why the DOM example above is preferred.
An alternative is to include <my-child>
in the <my-parent>
template instead of the slot, as mentioned in comments above:
render() {
return (
<Host>
<div class="some-content" style={this.getAccordionProps()}>
</div>
<my-child {...this.props()}></my-child>
</Host>
);
}
The problem with this approach is that it's not flexible to other usages (other content inside the parent), and may require updating both components if <my-child>
is updated. Also, if <my-child>
is only ever used this way, then whether it needs to be a separate component is questionable, because you could easily include it in <my-parent>
:
render() {
return (
<Host>
<div class="some-content" style={this.getAccordionProps()}>
</div>
{/* formerly my-child */}
<div ...>
<slot />
</div>
</Host>
);
}
Upvotes: 1