The difference between Lightning Components and Lightning Web Components, Part 3, Salesforce Data and the @wire decorator

Posted by

Welcome to Part 3 of the four part series covering the difference between Lightning Components and Lightning Web Components. If you missed Part 1, Markup and CSS or Part 2, JavaScript and Modules, I have linked them here for you to view on the blog.

To retrieve Salesforce data, Lightning web components use a reactive wire service built on Lightning Data Service. Lightning web components use the @wire decorator in their JavaScript class to read data from the wire adapters in the lightning/uiAPI module. Huh? Let’s unpack that. Before we jump into code, let’s establish some best practices.

Let’s say your use case is simple, like creating forms that let users view, edit, and create Salesforce records. Using the lightning-record-*-form components would be a great solution.

  • lightning-record-edit-form: Displays an editable form
  • lightning-record-view-form: Displays a read-only form
  • lightning-record-form: Supports edit, view and read-only modes

These components handle record CRUD changes without requiring Apex code. They also use Lightning Data Service to cache and share record updates across components. Basically, less time and less code means happier client and easier adaptability. This is a good thing!

So what happens if your application requires more flexibility? This is where you can leverage wire adapters from the lightning/uiAPI module. The lightning/uiAPI module includes wire adapters and JavaScript APIs for developers to leverage. Quick side note, what does wire actually mean and how does it work?

The wire service provisions an immutable (unable to be changed) stream of data to the component. Each value in the stream is a newer version of the value that precedes it. “We call the wire service reactive in part because it supports reactive variables, which are prefixed with $. If a reactive variable changes, the wire service provisions new data. We say “provisions” instead of “requests” or “fetches” because if the data exists in the client cache, a network request may not be involved.” ~Use Wire Service to Get Data – Lightning Web Component Developer Guide. Let’s get into some code:

// import necessary decorators from the lwc module
import { LightningElement, api, track, wire } from 'lwc';
// import the wire adapters you would like to leverage
import { getRecord, getFieldValue } from 'lightning/uiRecordApi';
// import the schema from your desired object/fields
import NAME_FIELD from '@salesforce/schema/Your_Object__c.Name';
import DESCRIPTION_FIELD from '@salesforce/schema/Your_Object__c.Description__c';
export default class ObjectJS extends LightningElement {
    @api recordId;
    @track objectName;
    @track objectDescription;
    // uses the wire adapter to get the record with the Id and the fields designated above
    @wire(getRecord, { recordId: '$recordId', fields: [NAME_FIELD, DESCRIPTION_FIELD] })
    wiredRecord({ error, data }) {
        if (data) {
            // Set your tracked properties with getFieldValue function
            // Argument 1: record data provided from the wire
            // Argument 2: field of choice
            this.objectName = getFieldValue(data, NAME_FIELD);
            this.objectDescription = getFieldValue(data, PICTURE_FIELD);
        } else if (error) {
            // Handle error. Details in error.message.
        }
    }
}

A few things to note:

  • It’s considered best practice by Salesforce to always import the references to the object and fields as you see on line 6 and 7. This will ensure that the referenced fields actually exist and will unearth any issues in advance so you aren’t stuck scratching your head.
  • As you can see on line 13, record Id is prepended with the $ character which tells the component to provision new data whenever changed. Pretty cool right?!

What if I simply wanted to create a new record? Let’s get the user more involved! That why we are here, right? Let’s say that we just built the markup for a user to create a new Contact that looks something like this:

<template>
    <lightning-card title="Create Contact" icon-name="standard:record">
        <div class="slds-m-around_medium">
            <lightning-input label="Id" disabled value={contactId}></lightning-input>
            <lightning-input label="First Name" onchange={onSetFirstName} class="slds-m-bottom_x-small"></lightning-input>
            <lightning-input label="Last Name" onchange={onSetLastName} class="slds-m-bottom_x-small"></lightning-input>
            <lightning-button label="Create Contact" variant="brand" onclick={createContact}></lightning-button>
        </div>
    </lightning-card>
</template>

Cool, we’ve got it looking pretty. Now lets put some power behind it!

import { LightningElement, track } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import { createRecord } from 'lightning/uiRecordApi';
// import the object reference
import CONTACT_OBJECT from '@salesforce/schema/Contact';
// import the field references
import FIRST_NAME_FIELD from '@salesforce/schema/Contact.FirstName';
import LAST_NAME_FIELD from '@salesforce/schema/Contact.LastName';
export default class createContact extends LightningElement {
    @track contactId;
    firstName;
    lastName;
    onSetFirstName(event) {
        this.firstName = event.target.value;
    }
    onSetLastName(event) {
        this.lastName = event.target.value;
    }
    createContact() {
        const recordInput = {
            apiName: CONTACT_OBJECT.objectApiName
            fields: { 
                [FIRST_NAME_FIELD.fieldApiName]: this.firstName,
                [LAST_NAME_FIELD.fieldApiName]: this.lastName,
            }
        };
        createRecord(recordInput)
            .then(contact => {
                this.contactId = contact.id;
                this.dispatchEvent(
                    new ShowToastEvent({
                        title: 'Success',
                        message: 'Contact was created! You are a wizard!',
                        variant: 'success',
                    }),
                );
            })
            .catch(error => {
                // Handle error. Details in error.message.
            });
    }
}

So cool! The createRecord function returns a promise that resolves after the record is created! Then, a nice toast event is fired to notify the user that their Contact was created.

Okay so this is all well and good in happy path, but what happens if I have a pre-existing @AuraEnabled method that I want to utilize for my Lightning web component. No problem! Let’s go over it. Side note: this is actually my preferred way. Let’s take a look back to how we would solve this using a Lightning Component. First, we need to write our @AuraEnabled apex method to use for both our Lightning component and Lightning web component.

public class ctrlContacts {

    @AuraEnabled
    public static List<Contact> getContacts(String recordId) {
        return [SELECT Id, FirstName, LastName FROM Contact WHERE Id = :recordId;
    }
}
//Lightning Component JS Controller Example
({
    loadContacts : function(component) {
        // use .get to retrieve attributes from component file
        var contactId = component.get("v.recordId");
        // apex method call
        var action = component.get("c.getContacts");
        // set your parameters
        action.setParams({
            "recordId": recordId,
        });
        action.setCallback(this, function (response) {
            // callback to get determine state of the response
            var state = response.getState();
            if (state === "SUCCESS") {
                var contacts = response.getReturnValue();
                component.set("v.Contacts", contacts);
            }
            else if (state === "INCOMPLETE") {
                // handle incomplete state
            }
            else if (state === "ERROR") {
                // handle error state
            }
        });
        $A.enqueueAction(action);
    }
})

If you already write Lightning components, this should look very familiar to you. If you are not, welcome! The Aura framework gets a lot of hate but know that this solved very complex problems for its time and even though we are transitioning to LWC, Lightning Components have done a lot for us and our clients. It’s time to see what this exact code would look like in a Lightning web component! There are three options: Using the wire service to call Apex and setting a property, Using the wire service with error handling and Calling the apex method directly.

// Using the wire service to call Apex and setting a property
import { LightningElement, api, track, wire} from 'lwc';
import getContacts from '@salesforce/apex/ctrlContacts.getContacts';
export default class getContacts extends LightningElement {

    @api recordId;
    
    @wire(getContacts, {recordId : '$recordId'})
    products;
}
// Using the wire service with error handling
import { LightningElement, api, track, wire} from 'lwc';
import getContacts from '@salesforce/apex/ctrlContacts.getContacts';
export default class getContacts extends LightningElement {

    @api recordId;
    @track error;
    @track contacts = [];
    
    @wire(getContacts, {recordId : '$recordId'})
    wiredContacts({ error, data }) {
        if (error) {
            this.error = error;
        } else if (data) {
            this.contacts = data;
        }
    }
}
// Calling the apex method directly
import { LightningElement, api, track} from 'lwc';
import getContacts from '@salesforce/apex/ctrlContacts.getContacts';
export default class getContacts extends LightningElement {

    @api recordId;
    @track error;
    @track contacts;
    
    // init lifecycle hook known as connectedCallback
    connectedCallback() {
        // Calling get contacts from the imported statement
        // Passing in arguments
        getContacts({recordId: this.recordId})
            .then(function(result) {
                // Setting contacts equal to result
                this.contacts = result;
            }.bind(this))
            // bind this???
    }
}

A few things to note:

  • With LWC, we are no longer limited to leveraging one controller. We can load in anything we want and query anything we want!
  • I wanted to make sure and show an example of passing in arguments into a method with an actual signature. Most of the examples I have seen take in nothing and thats not very helpful. Most everything we do involves arguments. Okay, rant over.
  • The connectedCallback function is part of the lifecycle hooks given to you by LWC. If you are not sure what these are, check out this link to get started!
  • What does bind mean and why are we passing it the this keyword? Good news! I am going to leave you hanging for a bit because in the next post, I will be discussing the this keyword more in depth.
  • I have experienced bugs with using @wire to call Apex and was told that by a Salesforce Engineer at TrailheaDX that its still buggy and likely will not be resolved until Summer ’19. For now, I would go with the second option if you keep getting ‘undefined’ returned.

In future posts, I will be also be showing effective ways to debug Lightning Web Components and useful chrome developer tools that will aid in your application becoming a success! As always, please let me know if you have any questions, I am always happy to help! Be on the lookout for Part 4, The difference between Lightning Components and Lightning Web Components, Composition and Events. See you soon.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s