Kishore
Kishore I am a Salesforce developer and a so-called blogger at SalesforceLwc.in. I love learning and sharing.

Picklist and Dependent Picklist in Salesforce LWC and AURA

Picklist and Dependent Picklist in Salesforce LWC and AURA

We will utilize base components that Salesforce has provided to build custom dynamic Picklist and Dependent Picklist.

Base component that we will be using:

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


We will be using the above base component to build custom dynamic picklist and dependent picklist components, which can be re-used anywhere just by passing some parameters. We will see how we can use that custom component in Aura, which covers AURA inter-operability as well.

Main Logic for dependent picklist:

We will reset the dependent picklist value on onchange handler of picklist, by firing pubsub events.

Before you even see the code experience LIVE component here:

Picklist.html

<template>
    <lightning-combobox
        id="pickList"
        name="progress"
        label={label}
        value={value}
        variant={variant}
        placeholder="Select"
        options={options}
        onchange={handleChange}>
    </lightning-combobox>
</template>


Picklist.js

/* eslint-disable no-console */
import { LightningElement, track, wire, api } from 'lwc';
import { getPicklistValuesByRecordType } from 'lightning/uiObjectInfoApi';
import { getObjectInfo } from 'lightning/uiObjectInfoApi';
import { CurrentPageReference } from 'lightning/navigation';
import { fireEvent } from 'c/pubsub';

export default class Picklist2 extends LightningElement {
    
    @wire(CurrentPageReference) pageRef;

    @api objectApiName;
    @api pickListfieldApiName;
    @api label;
    @api variant;

    /*only for lwc for mapping values in list and 
    also for mapping this with dependent picklist(give unique = record Id while using in dependent picklist)*/
    @api uniqueKey;

    @track value;
    recordTypeIdValue;

    @track options = [
        { label: 'Default 1', value: 'Default1' },
        { label: 'Default 2', value: 'Default2' },
        { label: '--None--', value: "" }
    ];   
    
    @api 
    get recordTypeId() {
        console.log("getter defaultRectype", this.recordTypeIdValue);
        return this.recordTypeIdValue;
    }
    set recordTypeId(value) {
        this.recordTypeIdValue = value;
        console.log("setter defaultRectype", this.recordTypeIdValue);
    }


    @api 
    get selectedValue() {
        console.log("getter", this.value);
        return this.value;
    }
    set selectedValue(val) {
        console.log("setter", val);
        if (val === '' || val === undefined || val === null)
            this.value = { label: '--None--', value: "" }.value;
        else
            this.value = val;
    }
         

    @wire(getObjectInfo, { objectApiName: '$objectApiName' })
    getRecordTypeId({ error, data }) {
        if (data) {
            this.record = data;
            this.error = undefined;
            if(this.recordTypeId === undefined){
                this.recordTypeId = this.record.defaultRecordTypeId;
            }
            console.log("Default Record Type Id", JSON.stringify(this.record.defaultRecordTypeId));
        } else if (error) {
            this.error = error;
            this.record = undefined;
            console.log("this.error",this.error);
        }
    }
                     
    @wire(getPicklistValuesByRecordType, { recordTypeId: '$recordTypeId', objectApiName: '$objectApiName' })
    wiredOptions({ error, data }) {
        if (data) {
            this.record = data;
            this.error = undefined;
            /*
            console.log("this.record picklist raw data", JSON.stringify(this.record));
            console.log("this.record.picklistFieldValues", JSON.stringify(this.record.picklistFieldValues));
            */
            if(this.record.picklistFieldValues[this.pickListfieldApiName] !== undefined) {

                let tempOptions = [{ label: '--None--', value: "" }];
                let temp2Options = this.record.picklistFieldValues[this.pickListfieldApiName].values;
                temp2Options.forEach(opt => tempOptions.push(opt));

                this.options = tempOptions;
            }
            console.log("this.options pick", JSON.stringify(this.options));
            if(this.selectedValue === '' || this.selectedValue === undefined || this.selectedValue === null) {
                this.value = { label: '--None--', value: "" }.value;
            } else {
                this.value = this.options.find(listItem => listItem.value === this.selectedValue).value;
            }
        } else if (error) {
            this.error = error;
            this.record = undefined;
            console.log("this.error",this.error);
        }
    }


    handleChange(event) {
        let tempValue = event.target.value;
        console.log("event.target.value",event.target.value);
        console.log("this.value",tempValue);
        let selectedValue = tempValue;
        let key = this.uniqueKey;

        //Firing change event for aura container to handle
        //For Self
        const pickValueChangeEvent = new CustomEvent('picklistchange', {
            detail: { selectedValue, key },
        });
        this.dispatchEvent(pickValueChangeEvent);

        //For dependent picklist
        let eventValues = {selValue : selectedValue, uniqueFieldKey: `${this.pickListfieldApiName}${this.uniqueKey}`};
        console.log("eventValues",JSON.stringify(eventValues));
        console.log("eventValues",eventValues);
        //Fire Pub/Sub Event, So that every other comp in the page knows the change
        fireEvent(this.pageRef, 'controllingValue', eventValues);
    }

}


Picklist.js-meta.xml

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata" fqn="picklist2">
    <apiVersion>46.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__AppPage</target>
        <target>lightning__RecordPage</target>
        <target>lightning__HomePage</target>
    </targets>   
</LightningComponentBundle>


Here we are using UI API to get record type id and picklist values based on that. UI API is helping us solve most of the problem, Otherwise previously should have to make an API call to get these results.

This is one more credit we can give to LWC for making our lives easier.

Above code is all about picklist what about dependent picklist ?? Hang on here comes it.

I have tried all different means to remove parent child hierarchy between picklist and dependent picklist, there is no direct relation, but has a bit of relation that is because of events. Dependent picklist depends on the event fired by picklist. When onchange event occurs in picklist it fires a pubsub event to notify dependent picklist that its value has changed, there by depdendent picklist changes its value to none. I have tries all different approaches to remove this relation but the final outcome wasn’t stable. That is the final outcome behaved differently in AURA and LWC, so had to to be content with this relation. Please leave your suggestions below to make this component even better. You can subscribe to the to my newsletter just by pressing on the ‘+’ plus button to bottom right corner, it is also a lightning web component which is inturn integrated to mailchimp which is a e-mail service.

Here comes dependent picklist code.

dependentPickList.html:

<template>
    <lightning-combobox id="dependentPickList"
                        name="Hello"
                        label={label}
                        value={value}
                        placeholder="--None--"
                        variant={variant}
                        options={options}
                        onchange={handleChange}>
    </lightning-combobox>
</template>


dependentPickList.js:

/* eslint-disable no-console */
import { LightningElement, track, wire, api } from 'lwc';
import { getPicklistValuesByRecordType } from 'lightning/uiObjectInfoApi';
import { getObjectInfo } from 'lightning/uiObjectInfoApi';
import { CurrentPageReference } from 'lightning/navigation';
import { registerListener, unregisterAllListeners } from 'c/pubsub';
import { NavigationMixin } from 'lightning/navigation';
import { fireEvent } from 'c/pubsub';

export default class DependentPickList4 extends NavigationMixin(LightningElement) {

    @wire(CurrentPageReference) pageRef;

    @api objectApiName; 
    @api pickListfieldApiName; //this field api name
    @api controllingFieldApi; //parent field api name that is controlling field api name
    //@api controllingFieldValue; //parent field value
    @api label;
    @api variant;

    recordTypeIdValue;
    controllingFieldVal;
    previousValue;
    prevControllingFieldVal;
    @track value;

    /*only for lwc for mapping values in list and 
    also for mapping this dependent picklist with another dependent picklist(give unique = record Id while using in dependent picklist)*/
    @api uniqueKey;

    connectedCallback() {
        console.log("In connected callback depndent");
        registerListener('controllingValue', this.handelControllingValue, this);
    }

    @api
    get controllingFieldValue() {
        return this.controllingFieldVal;
    }
    set controllingFieldValue(value) {
        this.controllingFieldVal = value;
        this.reinitiatemap();
    }


    disconnectedCallback() {
        console.log("In disconnected callback depndent");
        unregisterAllListeners(this);
    }


    @api 
    get recordTypeId() {
        console.log("getter defaultRectype", this.recordTypeIdValue);
        return this.recordTypeIdValue;
    }
    set recordTypeId(value) {
        this.recordTypeIdValue = value;
        console.log("setter defaultRectype", this.recordTypeIdValue);
    }

    @api 
    get selectedValue() {
        console.log("getter dependent", this.value);
        return this.value;
    }
    set selectedValue(val) {
        console.log("setter dependent", val);
        this.previousValue = this.value;

        if (val === '' || val === undefined || val === null){
            this.value = { label: '--None--', value: "" }.value;
        }
        else
            this.value = val;
    }

    handelControllingValue(valuesObj) {
        console.log("In handelControllingValue", JSON.stringify(valuesObj));
        if (`${this.controllingFieldApi}${this.uniqueKey}` === valuesObj.uniqueFieldKey) {
            if (valuesObj.selValue === '' || valuesObj.selValue === null || valuesObj.selValue === undefined) {
                this.selectedValue = '';
                this.options = [{ label: '--None--', value: "" }];
            } else {
                this.selectedValue = '';
                if (this.myMap !== null && this.myMap !== undefined) {

                    let tempOptions = [{ label: '--None--', value: "" }];
                        console.log("valuesObj.selValue", valuesObj.selValue);
                        if(this.myMap.get(valuesObj.selValue)) {
                            this.myMap.get(valuesObj.selValue).forEach(opt => tempOptions.push(opt));
                        }

                    this.options = tempOptions;
                }
            }

            let selectedValue = '';
            let key = this.uniqueKey;
            //Firing change event for aura container to handle
            //For Self
            const pickValueChangeEvent = new CustomEvent('picklistchange', {
                detail: { selectedValue, key },
            });
            this.dispatchEvent(pickValueChangeEvent);
            
            //For dependent picklist
            let eventValues = { selValue: '', uniqueFieldKey: `${this.pickListfieldApiName}${this.uniqueKey}` };
            //Fire Pub/Sub Event, So that every other comp in the page knows the change
            fireEvent(this.pageRef, 'controllingValue', eventValues);
        }
    }

    
    @track options = [
                      {label : 'Default 1', value : 'Default1'},
                      {label : 'Default 2', value : 'Default2'},
                      {label : '--None--', value : ''}
                     ];

    @track myMap = undefined;

    @wire(getObjectInfo, { objectApiName: '$objectApiName' })
    getRecordTypeId({ error, data }) {
        if (data) {
            this.record = data;
            this.error = undefined;
            if(this.recordTypeId === undefined){
                this.recordTypeId = this.record.defaultRecordTypeId;
            }
            console.log("Default Record Type Id", this.record.defaultRecordTypeId);
        } else if (error) {
            this.error = error;
            this.record = undefined;
            console.log("this.error",this.error);
        }
    }

    @wire(getPicklistValuesByRecordType, { recordTypeId: '$recordTypeId', objectApiName: '$objectApiName' })
    wiredOptions({ error, data }) {
        if (data) {
            this.record = data;
            this.error = undefined;

           let pickMap = new Map();

                if(this.record.picklistFieldValues[this.pickListfieldApiName] !== undefined) {
                    if(this.record.picklistFieldValues[this.pickListfieldApiName].controllerValues !== undefined) {

                        const controllerValues = this.record.picklistFieldValues[this.pickListfieldApiName].controllerValues;

                        Object.entries(controllerValues).forEach(([key, value]) =>  {
                            const picValues = this.record.picklistFieldValues[this.pickListfieldApiName].values;
                            picValues.forEach(pickValue => {
                                if(pickValue.validFor.includes(value)) {
                                    if(pickMap.has(key)){
                                        let temp = pickMap.get(key);
                                        temp.push(pickValue);
                                        pickMap.set(key, temp);
                                    }else {
                                        let temp2 = [];
                                        temp2.push(pickValue);
                                        pickMap.set(key, temp2);
                                        console.log("In inner else", temp2);
                                    }
                                }
                                    
                            });
                        });
                        console.log("MAP",pickMap);
                        this.myMap = pickMap;
                        console.log("In Wire", this.myMap);
                        console.log("etter controllingFieldValue",this.controllingFieldValue);
                        //Checking if selected and controlling values exist, and populating values accordingly
                        if (this.selectedValue) {
                            this.value = this.selectedValue;
                            if (!this.controllingFieldValue) {
                                this.options = [{ label: this.selectedValue, value: this.selectedValue }];
                                return;
                            }
                        }
                        else if (!this.controllingFieldValue) {
                            this.options = [{ label: '--None--', value: '' }];
                            this.value = this.options[0].value;
                            return;
                        }
                        else if(!this.selectedValue) {
                            
                            this.value = { label: '--None--', value: '' }.value;
                        }

                        this.initiateMap(pickMap);
                        console.log("Initial selectedValue ", this.selectedValue);

                    }else {
                        console.log("Error: This field is not a dependent picklist!");
                    }
                }else {
                    console.log("Error in fetching picklist values! Invalid picklist field");
                }
            console.log("***Initial Options*** ", JSON.stringify(this.options));
        } else if (error) {
            this.error = error;
            this.record = undefined;
            console.log("this.error",this.error);
        }
    }

    handleChange(event) {
        console.log("etter selected val in handle change", this.selectedValue);
        this.value = event.target.value;
        console.log("event.target.value",event.target.value);
        console.log("this.value",this.value);
        let selectedValue = this.value;
        let key = this.uniqueKey;
        //Firing change event for aura container to handle
        //For Self
        const pickValueChangeEvent = new CustomEvent('picklistchange', {
            detail: { selectedValue, key },
        });
        this.dispatchEvent(pickValueChangeEvent);

        //For dependent picklist
        let eventValues = {selValue : selectedValue, uniqueFieldKey: `${this.pickListfieldApiName}${this.uniqueKey}`};
        //Fire Pub/Sub Event, So that every other comp in the page knows the change
        fireEvent(this.pageRef, 'controllingValue', eventValues);
    }
    
    initiateMap(thisMap) {
        this.myMap = thisMap;

        if (thisMap !== null && thisMap !== undefined) {
            let tempOptions = [{ label: '--None--', value: "" }];
            if (this.controllingFieldValue !== null && this.controllingFieldValue !== undefined && this.controllingFieldValue !== '') {
                console.log("this.controllingFieldValue", this.controllingFieldValue);
                if(this.myMap.get(this.controllingFieldValue)) {
                    this.myMap.get(this.controllingFieldValue).forEach(opt => tempOptions.push(opt));
                }
            }
            this.options = tempOptions;
        }
        console.log("***Final Options dependent Picklist*** ", JSON.stringify(this.options));
    }
    
    
    reinitiatemap() {
        if (this.myMap !== null && this.myMap !== undefined){
            this.initiateMap(this.myMap);
            console.log("this.myMap length", this.myMap.length);
        }
    }

}


dependentPickList.js-meta.xml:

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata" fqn="dependentPickList4">
    <apiVersion>46.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__AppPage</target>
        <target>lightning__RecordPage</target>
        <target>lightning__HomePage</target>
    </targets>   
</LightningComponentBundle>


##Usage: Picklist:

<c-picklist2
     unique-key={account.Id} 
     object-api-name="Account" 
     record-type-id="0127F000000kyxEQAQ" 
     selected-value={account.Master_Picklist__c}  
     pick-listfield-api-name="Master_Picklist__c"
     onpicklistchange={handleMasterPicklistChange}>
</c-picklist2>


** Dependent picklist:**

<c-dependent-pick-list4
      unique-key={account.Id}
      object-api-name="Account"
      record-type-id="0127F000000kyxEQAQ" 
      pick-listfield-api-name="Controlling_Picklist__c"
      controlling-field-value={account.Master_Picklist__c}
      controlling-field-api="Master_Picklist__c"
      selected-value={account.Controlling_Picklist__c}
      onpicklistchange={handlePicklistChange}>
</c-dependent-pick-list4>


Hey guys, If you find this post interesting and helpful don’t forget to write your feedback down below in comments section. I am a social guy, you can find me in the apps mentioned below. Don’t forget to share this with other Salesforce folks. Don’t miss mentioning about Live components available in this site.

Picklist:

Picklist source code github link

DependentPicklist:

Dependent Picklist source code github link

Icons made by itim2101 from www.flaticon.com