
I am exploring on lightning web components framework and it’s underground standard JS concepts. As a first POC in LWC , I will be sharing with you, reusable dependent picklist components which can be used in custom LWC or Aura record forms.
Developed the component in three approaches by exploring the ways the picklist entries can be pulled from server side to web component UI.

- Dependent picklist using Apex
- Dependent Picklist using UI ObjectInfo API
- Dependent Picklist using Record Edit Form
Dependent picklist using Apex
First, I will develop a component which fetches the picklist entries through an apex method.
Step 1 – Create an apex method to fetch the picklist values.
Three parameters were shared to apex method to make it generic , they are object name, controlling and dependent picklist field api names. Wrapper class also be included with the class to share both picklist values in a useful format. Please go through PicklistValue class at the bottom of the class. Also, I have included the provisions for error handling but the wrapper should be modified accordingly (you can make the changes with respect to your requirement).
DependentPicklistController
public with sharing class DependentPicklistController { | |
@AuraEnabled(cacheable = true) | |
public static List<PicklistValue> getPicklistValues(String objApiName, String controlField, String dependentField){ | |
List<PicklistValue> pickListValues = new List<PicklistValue>(); | |
if(String.isBlank(objApiName) || String.isBlank(controlField) || String.isBlank(dependentField)) { | |
//return pickListValues; | |
objApiName = 'Account'; | |
controlField ='Country__c'; | |
dependentField= 'State__c'; | |
//enable the return statement and remove the above static assignment with some valid error value to update the UI | |
//return; | |
} | |
//Identify the sobject type from the object name using global schema describe function | |
Schema.SObjectType targetType = Schema.getGlobalDescribe().get(objApiName); | |
//Create an empty object based up on the above the sobject type to get all the field names | |
Schema.sObjectType objType = targetType.newSObject().getSobjectType(); | |
//fetch the all fields defined in the sobject | |
Map<String, Schema.SObjectField> objFieldMap = objType.getDescribe().fields.getMap(); | |
//Get the controlling and dependent picklist values from the objFieldMap | |
List<Schema.PicklistEntry> controlledPLEntries = objFieldMap.get(controlField).getDescribe().getPicklistValues(); | |
List<Schema.PicklistEntry> dependentPLEntries = objFieldMap.get(dependentField).getDescribe().getPicklistValues(); | |
// Wrap the picklist values using custom wrapper class – PicklistValue | |
for (Schema.PicklistEntry entry : controlledPLEntries) { | |
PicklistValue picklistValue = new PicklistValue(entry.isActive(), entry.isDefaultValue(), entry.getLabel(), entry.getValue()); | |
pickListValues.add(picklistValue); | |
} | |
//ValidFor is an indicator value for the controlling field which is base64 encrypted | |
//base64 value should be convered to 6bit grouped binary and 1 indicate the controlling field in a certain order | |
//Also,validFor value only be shown when it is serialized so it should be serialized then deserialized using PicklistValue wrapper class | |
for(PicklistValue plVal : deserializePLEntries(dependentPLEntries)) { | |
String decodedInBits = base64ToBits(plVal.validFor); | |
for(Integer i = 0; i< decodedInBits.length(); i++) { | |
// For each bit, in order: if it's a 1, add this label to the dependent list for the corresponding controlling value | |
String bit = decodedInBits.mid(i, 1); | |
if (bit == '1') { | |
PicklistValue dependentPLValue = new PicklistValue(plVal.active, plVal.defaultValue, plVal.label, plVal.value); | |
//Dependent picklist value is mapped to its parent controlling value through 'dependents' attribute in the PicklistValue wrapper class | |
if(pickListValues.get(i).dependents == null ) { | |
pickListValues.get(i).dependents = new List<PicklistValue>{dependentPLValue}; | |
}else{ | |
pickListValues.get(i).dependents.add(dependentPLValue); | |
} | |
} | |
} | |
} | |
return pickListValues; | |
} | |
private static List<PicklistValue> deserializePLEntries(List<Schema.PicklistEntry> plEntries) { | |
return (List<PicklistValue>) | |
JSON.deserialize(JSON.serialize(plEntries), List<PicklistValue>.class); | |
} | |
//Available base64 charecters | |
private static final String BASE_64_CHARS = '' +'ABCDEFGHIJKLMNOPQRSTUVWXYZ' +'abcdefghijklmnopqrstuvwxyz' +'0123456789+/'; | |
// Convert decimal to binary representation (alas, Apex has no native method 🙁 | |
// eg. 4 => '100', 19 => '10011', etc. | |
// Method: Divide by 2 repeatedly until 0. At each step note the remainder (0 or 1). | |
// These, in reverse order, are the binary. | |
private static String decimalToBinary(Integer val) { | |
String bits = ''; | |
while (val > 0) { | |
Integer remainder = Math.mod(val, 2); | |
val = Integer.valueOf(Math.floor(val / 2)); | |
bits = String.valueOf(remainder) + bits; | |
} | |
return bits; | |
} | |
// Convert a base64 token into a binary/bits representation | |
// e.g. 'gAAA' => '100000000000000000000' | |
private static String base64ToBits(String validFor) { | |
if (String.isEmpty(validFor)) { | |
return ''; | |
} | |
String validForBits = ''; | |
for (Integer i = 0; i < validFor.length(); i++) { | |
String thisChar = validFor.mid(i, 1); | |
Integer val = BASE_64_CHARS.indexOf(thisChar); | |
String bits = decimalToBinary(val).leftPad(6, '0'); | |
validForBits += bits; | |
} | |
return validForBits; | |
} | |
//Wrapper class | |
public class PicklistValue { | |
@AuraEnabled | |
public Boolean active { get; set; } | |
@AuraEnabled | |
public Boolean defaultValue { get; set; } | |
@AuraEnabled | |
public String label { get; set; } | |
@AuraEnabled | |
public String validFor { get; set; } | |
@AuraEnabled | |
public String value { get; set; } | |
@AuraEnabled | |
public List<PickListValue> dependents {get; set;} | |
public PicklistValue(){} | |
public PicklistValue(Boolean active, Boolean defaultValue, String label, String validFor, String value) { | |
this.active = active; | |
this.defaultValue = defaultValue; | |
this.label = label; | |
this.validFor = validFor; | |
this.value = value; | |
} | |
public PicklistValue(Boolean active, Boolean defaultValue, String label, String value) { | |
this.active = active; | |
this.defaultValue = defaultValue; | |
this.label = label; | |
this.validFor = validFor; | |
this.value = value; | |
} | |
public PicklistValue(String label, String value) { | |
this.label = label; | |
this.value = value; | |
} | |
} | |
} |
Step 2 – Create a lightning web component
The LWC component will consume the apex controller method for picklist values by using wire decorator. Required apex method parameters are created as public attribute (@api) in the component side for user accessibility.
<!– template with two combobox or dropdown base component | |
to show controlling and dependent picklist fields with its attributes–> | |
<template> | |
<lightning-layout horizontal-align="space"> | |
<lightning-layout-item padding="around-small"> | |
<lightning-combobox | |
name="progress" | |
label="Country" | |
value={parentValue} | |
options={controlOptions} | |
onchange={handleControlChange} ></lightning-combobox> | |
</lightning-layout-item> | |
<lightning-layout-item padding="around-small"> | |
<lightning-combobox | |
class="dependent" | |
name="progress" | |
label="State" | |
value={dependentValue} | |
options={dependentOptions} | |
onchange={handleDependentChange} disabled={isDisabled}></lightning-combobox> | |
</lightning-layout-item> | |
</lightning-layout> | |
</template> |
import { api, LightningElement, track, wire } from 'lwc'; | |
import getPicklistValues from '@salesforce/apex/DependentPicklistController.getPicklistValues'; | |
const defaultOption = {label : '—None—',value: ''}; | |
export default class DependentPicklist extends LightningElement { | |
//public attribute for object selection | |
@api | |
objectApiName; | |
//Public attribute for controlling picklist field | |
@api | |
controlFieldName; | |
//Public attribute for dependent picklist field | |
@api | |
dependentFieldName; | |
//Public attribute for selected dependent picklist value | |
@api | |
dependentValue = ''; | |
//Public attribute for selected controlling picklist value | |
@api | |
parentValue =''; | |
//to stroe controlling picklist value options | |
controlOptions =[]; | |
//to stroe dependent picklist value options based on the controlling picklist value | |
dependentOptions = []; | |
data = []; | |
//to enable/disable the dependent picklist field in the UI | |
isDisabled = true; | |
/* | |
Wiring to getPicklistValues method defined in the apex class – DependentPicklistController | |
which returns picklist values based on the paramteres object name, controlling Field and dependent field name */ | |
@wire(getPicklistValues,{objApiName : '$objectApiName', controlField : '$controlFieldName', dependentField:'$dependentFieldName'}) | |
picklistData({data,error}) { | |
if(data){ | |
this.data = data; | |
this.updateOptions(); | |
//console.log(JSON.stringify(data)); | |
} | |
} | |
//Sets the controlling field options to show | |
updateOptions() { | |
let defaultValue = ''; | |
this.controlOptions = this.data.map((option) => { | |
if(option.defaultValue) { | |
defaultValue = option.value; | |
} | |
return { label: option.label, value: option.value } | |
}); | |
//show the default selected option as the first item in the dropdown otherwise –None– | |
if(defaultValue){ | |
this.controlOptions.push(defaultOption); | |
this.parentValue = defaultValue; | |
this.updateDependentOptions(); | |
this.isDisabled = false; | |
}else{ | |
//default –None– Option is already set in the defaultOption constant | |
this.controlOptions.unshift(defaultOption); | |
} | |
} | |
//set the dependent options in the dropdown field | |
updateDependentOptions() { | |
let defaultValue = ''; | |
//fetch the selected option in the controlling field with it's dependent picklist values | |
const selOptionDetail = this.data.filter(option => option.value === this.parentValue); | |
//check the dependent option list empty or not and sets in to the dependent field, | |
//filter function returns an array so fetching the value in the 0th index | |
if(selOptionDetail.length > 0 && selOptionDetail[0].dependents) { | |
this.dependentOptions = selOptionDetail[0].dependents.map((option) => { | |
if(option.defaultValue) { | |
defaultValue = option.value; | |
} | |
return { label: option.label, value: option.value } | |
}); | |
//sets the default selected option or –None– | |
if(defaultValue){ | |
this.dependentOptions.push(defaultOption); | |
this.dependentValue = defaultValue; | |
}else{ | |
this.dependentValue = ''; | |
this.dependentOptions.unshift(defaultOption); | |
} | |
//console.log(this.dependentOptions); | |
} | |
} | |
/*Handler for onchange event which initiate a custom event and set the value in the corresponding output attribute */ | |
handleControlChange(event) { | |
this.parentValue = event.detail.value; | |
//enable the dependent picklist field – only needed for initial selection | |
this.isDisabled = false; | |
//update the dependent field option with newly selected control field value | |
this.updateDependentOptions(); | |
this.dispatchEvent(new CustomEvent('controlchange',{detail : {value : this.parentValue}})); | |
} | |
/*Handler for onchange event which initiate a custom event and set the value in the corresponding output attribute */ | |
handleDependentChange(event) { | |
this.dependentValue = event.detail.value; | |
this.dispatchEvent(new CustomEvent('dependentchange',{detail : {value : this.dependentValue}})); | |
} | |
} |
<?xml version="1.0" encoding="UTF-8"?> | |
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata"> | |
<apiVersion>49.0</apiVersion> | |
<isExposed>true</isExposed> | |
<targets> | |
<target>lightning__AppPage</target> | |
<target>lightning__RecordPage</target> | |
<target>lightning__HomePage</target> | |
</targets> | |
</LightningComponentBundle> |
‘dependentValue’ and ‘parentValue’ are the output parameters for providing the dependent and controlling picklist field selected values. Also, the selections will be notified with separate custom events for both the fields, named ‘controlchange’ and ‘dependentchange’.


Please refer below documents for more details:
https://developer.salesforce.com/docs/atlas.en-us.api_meta.meta/api_meta/meta_picklist.htm
https://developer.salesforce.com/docs/component-library/documentation/en/lwc
Hope you guys are able to understand the implementation and the code is self explanatory. Also, I have included the necessary comments for most of the lines. Use the comment section for posting your valuable feedback and questions.
Read my next blog for same component implementation using Record edit form / UIObjectInfo api.
!!!Stay safe !!! Stay Home !!!
Pingback: Custom Dependent Picklist in LWC – Part 2 – Tech Evangel