AlexJanow
AlexJanow

Reputation: 97

How can I pass down props from a parent to children in a slot using Stencil.js?

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

Answers (2)

Jakub Zavazal
Jakub Zavazal

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

G. Tranter
G. Tranter

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

Related Questions