Monday, July 15, 2019

Display Combobox values by Lightning Web Components and propagating events to Parent Aura Components

Motivation behind this


I have started exploring on Lightning Web Components (aka. LWC) lately and tried to answer this question at StackExchange Lightning Web Component Combobox in a different way. During my proof of concepts I have faced few issues in array handling on LWC js controller and thought of sharing this solution.

I have come up with a use case which could help many of us who are learning LWC. Now a days, there are lot of Lightning upgrade projects are going on. Most of companies are also transforming Aura Components to Lightning Web Components. Also, in some cases, Aura Components will still exist as Parent Component of LWC child components. So, communication from Child LWC to Parent Aura Component is also necessary which has been depicted as part of this use case.

Use Case


In a Salesforce instance there is a Aura Component which displays Account specific information and all types of DML operation are getting performed from this component. There is a requirement to display list of Contacts pertaining to that Account which should be done on Lightning Web Components (as there could many other Contact specific information or calculations can be performed) and selection of the Contact from the dropdown should fire an event and send the ContactId to the Parent Aura Component.

End output will look like this:


Solution Approach


We can break down the solutions into the smaller steps as follows:

1. Create a Lightning Web Component which will display list of Contacts from database. It will also take AccountId as parameter to this component.

2. Create a Aura Component which will have Account information and it will embed Child LWC and pass AccountId to the child.

3. Final step will be event propagation which will be from Child LWC to Parent Aura component. Here, for sake of simplicity, selecting Contact from comboxbox will fire Component event and send ContactId of the selected item and pass it as part of event. This will be displayed into Parent Aura Component as a text.

Child Web Components

displayContact.html

As part of step 1, create this LWC component and place lightning-combo component which attributes. Note that, value, options attributes and onchange event handler.

<template>
    <lightning-card title="List of Contacts - Lightning Web Component" icon-name="custom:custom63">
        <lightning-combobox
            name="contacts"
            label="Contacts"
            placeholder="Choose Contact"
            value={value}
            onchange={handleChange}
            options={statusOptions}>
        </lightning-combobox>
    </lightning-card>
</template>

displayContact.js

This is most important for implementing this functionality.

There are the points to meet Step 2, to populate and display combobox values.

  • import LightningElement, api, wire, track which will be used in this class.
  • import getContacts as we will be using getContacts method of AccountController apex class.
  • At the class level, 
    • recordId which is public property decorated with @api
    • error, items array, value are private tracked properties
  • @wire to function i.e getContacts and passing parameter '$recordId' for accountId
    • This is function, retrieve the data and prepare items array with value and label which should be recognized and used in the combobox.
    • Mind the syntax for storing values in array: this.items = [...this.items ,{value: data[i].Id , label: data[i].Name}]
  • getter property for statusOptions which displays options taking items array.

To meet Step 3, we have used handleChange(event) it fires based on changing combobox item selection.

  • Collect combobox value as selectedOption.
  • Create a Custom Event, Lightning web component fires a custom filterchange event in its JavaScript file.
  • And finally, dispatchEvent which helps to communicate from child to parent.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
/* eslint-disable no-console */
/* eslint-disable no-alert */
/* eslint-disable no-unused-vars */
import { LightningElement, api, wire, track } from 'lwc';

import getContacts from '@salesforce/apex/AccountController.getContacts';
let i=0;

export default class DisplayContact extends LightningElement {
     /** Id of record to display. */
    @api recordId; //this captures AccountId which is passed from Parent Component
    @track error;   //this holds errors

    @track items = []; //this holds the array for records with value & label

    @track value = '';  //this displays selected value of combo box

    /* Load Contacts based on AccountId from Controller */
    @wire(getContacts, { accountId: '$recordId'})
    wiredContacts({ error, data }) {
        if (data) {
            for(i=0; i<data.length; i++) {
                console.log('id=' + data[i].Id);
                this.items = [...this.items ,{value: data[i].Id , label: data[i].Name}];                                   
            }                
            this.error = undefined;
        } else if (error) {
            this.error = error;
            this.contacts = undefined;
        }
    }
   
    //getter property from statusOptions which return the items array
    get statusOptions() {
        console.log(this.items);
        return this.items;
    }

    handleChange(event) {
        // Get the string of the "value" attribute on the selected option
        const selectedOption = event.detail.value;
        console.log('selectedOption=' + selectedOption);

        //This is for event propagation
        
        const filterChangeEvent = new CustomEvent('filterchange', {
            detail: { selectedOption },
        });

        
        // Fire the custom event
        this.dispatchEvent(filterChangeEvent);
    }
    
}

AccountController.cls

getContacts method returns List of Contacts based on SOQL query. We are not doing any data transformation which can be recognized in combobox display, rather let it be handled by LWC js controller as specified above.


public with sharing class AccountController{
    
    @AuraEnabled (cacheable=true)
    public static List<Contact> getContacts(String accountId){
        return [SELECT Id, Name FROM Contact WHERE AccountId =:accountId];        
    }
}

displayContact.js-meta.xml

This is nothing fancy as such.

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

Now its time to prepare Parent Aura Component and related files.

ParentAccount.cmp

Following points to be noted, it meets Step 3:

  • force:hasRecordId has been implemented to get recordId of Account.
  • That has been passed as recordId which is captured in Child LWC controller through @api public property
  • The enclosing Aura component wrapper adds a handler for the custom event. Notice that the event handler, onfilterchange, matches the event name with “on” prefixed to it. That is, use onfilterchange to handle the event named filterchange.


<aura:component implements="flexipage:availableForAllPageTypes,force:hasRecordId">
    <aura:attribute name="message" type="String" default="Select a Contact from dropdown"/>
    <lightning:card title="Account - Aura Component" iconName="custom:custom30">
        <div class="slds-m-around_medium">
            <lightning:layout>
                <lightning:layoutItem size="4">
                    <!-- This is an LWC component -->
                    <c:displayContact recordId="{!v.recordId}"  onfilterchange="{!c.handleFilterChange}"/>
                </lightning:layoutItem>
                <lightning:layoutItem size="8" class="slds-p-left_medium">
                    {!v.message}
                </lightning:layoutItem>
            </lightning:layout>
        </div>
    </lightning:card>
</aura:component>


ParentAccountController.js

The Aura component’s controller receives the event. You can handle the event however you’d like. This component operates on the filters array passed in the details property of the Lightning web component event. You can optionally fire a new Aura event to communicate with other Aura components.


({
    handleFilterChange: function(component, event) {
        var filters = event.getParam('selectedOption');
        console.log('filters=' + filters);
        component.set('v.message', filters.length > 0 ? 
                      'Selected ContactId from Child LWC: ' + filters : 
                      'No selection from Child LWC');
    },
});

So, we are done.

After deployment, place ParentAccount component on the page layout of Account and our desired functionality will be achieved.

Note: The concepts for preparing array with items not only applicable for combobox, but also for, lightning-checkbox-group, lightning-radio-grouplightning-dual-listbox, lightning-datatable etc. So the above approach can be leveraged for all those purposes.

References


5 comments:

  1. Nice post @Santanu Sir.

    I also tired one post on Event in Lightning Web Components.

    http://amitsalesforce.blogspot.com/2019/07/events-in-lightning-web-components-lwc.html

    ReplyDelete
  2. @Amit, I have seen your excellent post on events.

    ReplyDelete
  3. @santanu sir very good usage of spread syntax but only one correction i think is required is to change the application event mentioned in one of the paragraph with component event as we are firing comp event in the above example.

    ReplyDelete
    Replies
    1. @Debarun, Thanks for informing me. This should be component event and I have updated the post.

      Delete
  4. The information which you have provided is very good. It is very useful who is looking for salesforce certification cost

    ReplyDelete