2

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?

3

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 (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);
        }
    }
}
  • Awesome I will give it a try – gNerb May 3 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 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 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 at 16:48
  • 1
    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 at 16:50
2

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);
    }
  }

}
  • 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 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 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 at 17:59
  • you can even include handleChange(event) and button to make it such that it updates the record. – compski Oct 15 at 1:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.