7

In LWC, is it possible to get a pick-list field's possible values?

I'm writing a component that is embded into a lightning record page. I need my component to be able to load the record from the page, get data from the fields and use that data to pre-populate my search form with default values.

I have this working with city/state and checkbox fields. Right now the city/state fields are built pull data from an aggregate query of contacts to find all possible state/city combinations (I know this is doomed to fail when the records reach over 50k contacts but Its good enough for now).

I have a pick-list field on my object called skill level, I need to be able to build a combo box that has all the possible values for skill level and set the default value to the value on the associated record.

I'd like to avoid hard-coding the available options if possible.

In LWC, is it possible to get a pick-list field's possible values?

2 Answers 2

9

LWC does have a wire adapter that pulls in picklist values dynamically.

import { LightningElement, wire, track } from 'lwc';
import { getPicklistValues } from 'lightning/uiObjectInfoApi';
import INDUSTRY_FIELD from '@salesforce/schema/Account.Industry';
import {getRecord, getFieldValue} from 'lightning/uiRecordApi';

const fields = [INDUSTRY_FIELD];

export default class Example extends LightningElement {
    @api recordId;
    @track selectedOption; 
    @track options;

   /*
    * There is no guarantee that the pick-list values will return first
    * Because of this, we check to see if a selected option already exists
    * If it does, we verify it is a valid value for the picklist.
    * If it is not valid, we set the selected option to the default value.
    * If it is valid, no changes are made
    * If there is no selected option, we use the default option
    *
    * Note: recordTypeId is required. If your object has no record types create
    * at least 1.
    */
    @wire(getPicklistValues, { recordTypeId: '012000000000000AAA', fieldApiName: INDUSTRY_FIELD })
    setPicklistOptions({error, data}) {
        if (data) {
            this.options = data.values;

            if (this.selectedOption) {
                let optionIsValid = this.options.some(function(item) {
                    return item.value === this.selectedOption;
                }, this);

                if (!optionIsValid) {
                    this.selectedOption = data.defaultValue;
                }
            } else {
                this.selectedOption = data.defaultValue;
            }
        } else if (error) {
            console.log(error);
        }
    }


    /*
     * As with above, there is no guarantee to which order these functions execute
     * If the options list exists, we will check to see if our value is valid.
     * If it is, set our value as the selected value
     * If it is not, do nothing
     * If the options list does not exist, we will create an options list with 
     *   a single value using this records picklist value.
     */
    @wire(getRecord, {recordId:'$recordId', fields})
    account({error, data}) {
        if (data) {
            let industryValue = getFieldValue(data, INDUSTRY_FIELD);

            if (!this.options) {
                this.options = [{label:industryValue, value:industryValue}];
            }

            let industryIsValid = options.some(function(item) {
                return item.value === industryValue;
            }, this);

            if (industryIsValid) {
                this.selectedOption = industryValue;
            }
        } else if (error) {
            console.log(error);
        }
    }
}
8
  • Awesome I will give it a try
    – gNerb
    May 3, 2019 at 1:55
  • Updated the answer to show more code on how to accomplish what the question was asking about. I also added some of the nuances I saw while testing.
    – gNerb
    May 3, 2019 at 15:57
  • @gNerb ah yes, you found the nuances i vaguely recalled. In instances like this, I would prefer deferring to a getter/setter function on invoking downstream changes of wires. You can manipulate it so that @wires bind to a private (i.e. _recordId) var which is only set when the preceding function finishes. Think of it like a promise workaround, whereby the "resolve" is you setting a private var from null to truthy.
    – tsalb
    May 3, 2019 at 16:45
  • I tried getters and setters a few times but they never seemed to work they way I expected them to.
    – gNerb
    May 3, 2019 at 16:48
  • 2
    That's good to know, eventually I will use this adapter so I might give the getter/setter pattern a shot and maybe add an addendum answer to this one.
    – tsalb
    May 3, 2019 at 16:50
3

I had some free time this morning, and this is how you can control wire adapter run order. I misspoke earlier about requiring usage of use getter/setters, the important bit is actually how you forcefully (re)invoke the wire.

Wires are invoked based on some kind of change (the reactive part) and a null to truthy change is perfectly valid way to (re)invoke it.

If you inspect both error and data when supplied with a falsey / undefined reactive var when the component is constructed, both come back undefined and it fails silently (this is good).

However, something like this will (re)invoke a wire adapter:

<template>
  <lightning-combobox
    label="Example"
    value={selectedOption}
    options={options}
  ></lightning-combobox>
</template>
import { LightningElement, api, wire, track } from 'lwc';
import { getPicklistValues } from 'lightning/uiObjectInfoApi';
import TYPE_FIELD from '@salesforce/schema/Account.Type';
import { getRecord, getFieldValue } from 'lightning/uiRecordApi';

const fields = [TYPE_FIELD];

export default class Example extends LightningElement {
  @api recordId;
  @track selectedOption;
  @track options;

  // private
  _recordTypeId;

  // Step 1
  @wire(getRecord, {recordId:'$recordId', fields})
  account({error, data}) {
    if (data) {
      let typeValue = getFieldValue(data, TYPE_FIELD);
      this.selectedOption = typeValue;
      this._recordTypeId = '012000000000000AAA'; // setting this value will re-invoke the wire
    } else if (error) {
      console.log(error);
    }
  }

  // Step 2, determined by when the reactive bind is changed
  @wire(getPicklistValues, { recordTypeId: '$_recordTypeId', fieldApiName: TYPE_FIELD })
  setPicklistOptions({error, data}) {
    if (data) {
      // Apparently combobox doesn't like it if you dont supply any options at all.
      // Even though selectedOption was assigned in step 1, it wont "select" it unless it also has options
      this.options = data.values;
    } else if (error) {
      console.log(error);
    }
  }

}
4
  • FYI there is a longer answer using getters and setters (on this.selectedOption setting) that can similarly assign a value to this._recordTypeId, but it became really verbose and this was cleaner. In other words, you can force a getter/setter pattern but it is not required.
    – tsalb
    May 3, 2019 at 17:48
  • This is definitely interesting. The approach I used was to make it so that it doesn't matter what order they fire in as opposed to forcing them to fire in a given order. If you take out the extra validation code I have, it's not much more verbose. You could also argue that there is a bit of efficiency to mine as both calls can be made simultaneously as opposed so serially.
    – gNerb
    May 3, 2019 at 17:57
  • Agreed there on your design. For sanity though, I tend to rely on promise patterns to know ahead of time when I can expect data and run order. I typically dont rely on run order in wire modules, rather, i want a specific point in time to execute a wire post or during connectedCallback() and leverage both the reactive and caching featuresets in my ui stack, something that is not available imperatively.
    – tsalb
    May 3, 2019 at 17:59
  • you can even include handleChange(event) and button to make it such that it updates the record.
    – compski
    Oct 15, 2019 at 1:56

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .