[SalesForce] Parent –> Child –> grand child communication using template.queryselector

I am having a parent component called providerHome and it has one lightning combo box.
Inside providerHome I have child component as dynamicElement and inside dynamic Element I am loading multiPicklist

ProviderHome –> dynamicElement –> multiPicklist

On onchange event of providerHome combobox I want to pass some values for validation so I am using this approach

in providerHome.js

 picklistHandleChange(event) {    
    const dependantValues = {
      controllerEvent: event.target.name,
      inputDesc: this.inputDesc
    };
    this.template
      .querySelector("c-dynamic-element")
      .constructMultiDropDown(dependantValues);  
  }

Inside dynamicElement.js

@api constructMultiDropDown(values) {
   // console.log("values :>> ", JSON.stringify(values));
    this.template
      .querySelector("c-multi-pick-list")
      .multiDropDownConstruct(values);
  
  }

In multiPickList.js

 @api
  multiDropDownConstruct(values) {
    console.log("Reached grand child:>>",values);
  }

From parent –> child its working fine but from Child –> grand child I am always getting error.
I cant go for @api approach as this dynamic element component is responsible for loading all lightning components like lightning-input,lightning-combobox etc

I tried this also and not working
in providerHome.js

 picklistHandleChange(event) {    
    const dependantValues = {
      controllerEvent: event.target.name,
      inputDesc: this.inputDesc
    };
    this.template
      .querySelector("c-dynamic-element")
      .querySelector("c-multi-pick-list")
      .multiDropDownConstruct(dependantValues);  
  }

Error

enter image description here


I tried by getter/setter. When the page loads I am getting console.log in grand child. After that if i change the drop down in parent component , getter/setter in grand child is not getting fired . Not sure what is the issue

In child(dynamicElement.js)

 @track childDependant = "Initialized";

  @api constructMultiDropDown(values) {
    console.log("Fired becoz of parent combobox change");
    this.childDependant = "Changed";   
  }

HTML

<template>
  <c-multi-pick-list
    label={attrs.label}
    options={attrs.options}
    disabled={attrs.disabled}
    showclearbutton="true"
    showfilterinput="true"
    showrefreshbutton="true"
    from-parent={childDependant}
  ></c-multi-pick-list>
</template>

In grand child (multiPicklist.js)

 _dependantValue;
  @api get fromParent() {
    console.log(this._dependantValue, "this._dependantValue");
    return this._dependantValue;
  }
  set fromParent(value) {
    this._dependantValue = value;
  }

enter image description here


Actually I am using only one template to render multiple fields

  <template for:each={dataForDynamicComponentCreation} for:item="item">
        <div key={item.requiredKey} class="slds-size_1-of-4">
          <div class="slds-m-around_x-small">
            <c-dynamic-element
              attrs={item.attributes}
              tag={item.componentDef}
            ></c-dynamic-element>
          </div>
        </div>
      </template>

So if a lightning-input is created it will created inside <c-dynamic-element> and similarly all will be created inside </c-dynamic-element>

So in order to find out the method inside Multipicklist component I have to do querySelectorAll, loop through it and find out the place where multi picklist is located and then fire. Instead I just did queryselector so always it searched for method in first component(lightning-input) instead where multipicklist is located.

Best Answer

I have tried a sample code to pass data from parent ===>> child===>> grandchild in Lightning Web Component.

I have used two ways to do so. The first is @api property and another one using querySelector. Both works fine. The below code pass a value from parent to child and then child to grandchild.

parentComponent.html

<template>
    <lightning-combobox name="progress" label="Status in Parent" value={value} placeholder="Select Progress" options={options} onchange={handleChange} >
    </lightning-combobox>

    <hr></hr>
    <c-child-component value-from-parent={value}></c-child-component>
</template>

parentComponent.js

import { LightningElement } from 'lwc';

export default class ParentComponent extends LightningElement {
    value = 'inProgress';

    get options() {
        return [
            { label: 'New', value: 'new' },
            { label: 'In Progress', value: 'inProgress' },
            { label: 'Finished', value: 'finished' },
        ];
    }

    handleChange(event) {
        this.value = event.detail.value;
        this.template.querySelector('c-child-component').handleChange(this.value);
    }
}

childComponent.html

<template>
    <lightning-combobox name="progress" label="Status in Child" value={value} placeholder="Select Progress"
            options={options} onchange={handleChange} >
    </lightning-combobox>

    <hr></hr>
    The value from Parent is {valueFromParent}

    <hr></hr>
    <c-grand-child-component value-from-child={value}></c-grand-child-component>
    
</template>

childComponent.js

import { LightningElement, api } from 'lwc';

export default class ChildComponent extends LightningElement {
    value = 'inProgress';
    @api valueFromParent;

    get options() {
        return [
            { label: 'New', value: 'new' },
            { label: 'In Progress', value: 'inProgress' },
            { label: 'Finished', value: 'finished' },
        ];
    }

    @api
    handleChange(valueFromParent) {
        this.value = valueFromParent;
        this.template.querySelector('c-grand-child-component').handleChange(valueFromParent);
    }
}

grandChildComponent.html

<template>
    <lightning-combobox name="progress" label="Status in Grand Child"
            value={value} placeholder="Select Progress" options={options} onchange={handleChange} >
    </lightning-combobox>

    <hr></hr>
    The value from Parent is {valueFromChild}
</template>

grandChildComponent.js

import { LightningElement, api } from 'lwc';

export default class GrandChildComponent extends LightningElement {
    value = 'inProgress';

    @api valueFromChild;

    get options() {
        return [
            { label: 'New', value: 'new' },
            { label: 'In Progress', value: 'inProgress' },
            { label: 'Finished', value: 'finished' },
        ];
    }

    @api
    handleChange(valueFromParent) {
        this.value = valueFromParent;
    }
}

Demo Video

enter image description here

Related Topic