Need to learn all about Lightning Web Components (LWC’s)?

For anyone brand new to the Salesforce Lightning Web Component Framework, learning about all this can be quite challenging. Late last year I released a Pluralsight course named, “Salesforce Lighting Web Components: The Big Picture“.

This Big Picture course was designed to slow things down and give you a high-level overview of all the pieces that make up the framework. In a nutshell, Lightning Web Components can be used to build trusted and highly performant web apps that use Salesforce data to run anywhere.

In an effort to expose more developers to this powerful framework, I have put together the following promotional preview video that you can watch for free on this blog. I hope it helps give you a better and bigger picture of all that LWC’s have to offer.

Salesforce Lightning Web Components: The Big Picture

Top 5 Tips for Building Your First LWC

I am very honored to have been asked by 100daysoftrailhead.com to record a session about Top 5 Tips for Building Your 1st LWC. And with that, I wanted to write this post to summarize the tips that I featured in that presentation.

Tip # 1 – Hit the Trail

If you are brand new to Lightning Web Components, then you definitely want to begin by working through the Build Lightning Web Components trail on Trailhead. The trail several projects and modules and you do not have to work them all at once. But at the very least, you should complete the first one, Quick Start: Lighting Web Components.

Tip # 2 – Install the Salesforce Extended Expansion Pack

Better Option is to install the Salesforce Extended Expansion Pack
Better Option is to install the Salesforce Extended Expansion Pack

The official Salesforce docs tell you to install the Salesforce Extension Pack for Visual Studio Code. But, I suggest you also/or instead install the Salesforce Extended Expansion Pack, which you can do by Clicking the Extensions icon in the left toolbar of Visual Studio Code and then selecting Salesforce Extension Pack (Extended).

Select the Extended Expansion Pack though the Extensions icon in Visual Studio Code
Select the Extended Expansion Pack though the Extensions icon in Visual Studio Code

This extension pack will include 4 additional JavaScript libraries that you will more than likely need. They are XML, ESLint, Prettier and Apex PMD.

Tip # 3 – Embrace the Command Line Interface (CLI) Help

Don’t fear the CLI – Embrace it! I know that the Salesforce Extensions offers a very nifty Command Palette tool, but that does not cover everything that the CLI offers. By using the built-in help features, you not only get access to the latest docs (and that you can be rest assured of), but you can learn a lot about what the CLI Offers.

To access the help feature, just type the following sfdx help from a Terminal Window in Visual Studio Code. This will bring up results such as the following:

Access the CLI help feature from Terminal window
Access the CLI help feature from Terminal window

To drill down into one of the topics, such as the force one, use the following:

sfdx force --help

From there you can drill down as far as you need to, such as this command for accessing info about creating a Salesforce DX project:

sfdx force:project:create --help

Tip # 4 – Use Base Lightning Components Whenever Possible

The Base Lightning Components that Salesforce offers not only make your life as a developer so much easier, they are highly performant than anything you might try to create yourself. So, you should check them all out and make sure to use them whenever possible.

The Component library offers a handy Lightning Mini Playground that you can use to access sample HTML and JavaScript directly.

The Component Library offers a handy Lightning Mini Playground feature

Tip # 5 – Reference the Code in the Sample App Gallery

The Sample App Gallery includes real-world code that were all designed by the incredible developers with the Salesforce developer relations group. They not only demonstrate new Salesforce features, but best practices for how to create Lightning web components.

As you begin the process, the most important one to checkout is the LWC Recipes one. This GitHub repo features very short code snippets that demonstrate how to perform certain key operations.

Bonus Tip – Check out My New Pluralsight Course

This month, I also released my latest Pluralsight course, “Salesforce Lightning Web Components: The Big Picture“. You can find out more about it in this post. Once you have completed that course, you might want to checkout, “Building Your First Lightning Web Component (LWC) for Salesforce“.

New Pluralsight Course: Salesforce Lightning Web Components: The Big Picture

I am beyond happy to announce the release of my latest Pluralsight course, “Salesforce Lightning Web Components: The Big Picture“. This is a high-level overview of all the important things that make up this new modern and standards-based framework.

Viewers will first explore what makes up the entire Lightning Web Stack. This will include discovering the open-source Lightning Design System, which is key to the entire Lightning Experience.

Lightning Web Stack
Lightning Web Stack

Learners will also learn about the modern developer tools that Salesforce now offers. These tools offer developers a way to build robust and high-performing web apps. The tools should be instantly familiar to developers familiar with building modern web apps using frameworks like React or Vue.

Visual Studio Code and the Salesforce Extension Pack
Visual Studio Code and the Salesforce Extension Pack

When learners are finished, they should have the skills and knowledge of Lightning Web Components needed to build an adoption plan for their own Salesforce organizations.

And if you watch the course, please feel free to give me feedback. Good or bad. Thanks!

EDIT: Go here to access a promotional video I created for this course. It is a condensed 10 minute preview of the course that should give you a good idea of what it offers.

Lightning Tip: Always Create Admin Friendly Lightning Components

In case you did not realize, Salesforce Lightning components can be one of two flavors:

  1. Aura components – Launched in 2015 and up until early 2019 were called Lightning components.
  2. Lightning web components (LWC’s) – Launched in 2019 and are considered the future of Salesforce development.

Design for Configurability

Whichever flavor you select, the tip in this post applies to both Aura components and the new Lightning web components.

So what does creating Admin friendly Lightning components mean?

In a nutshell, it means making your component as configureable as possible to anyone using it to assemble Lightning pages using Lightning App Builder. This is done by using a meta configuration file for LWC’s or a design resource file for Aura components.

Whichever flavor you chose, the tip in this post applies to both Aura components and the new Lightning web components.

To give you an example of how that is done for LWC’s, let’s look at the code for the eBikes sample app, which can be found here for the LWC version. This is what the HTML for that component looks like:

<template>
    <div class="slds-card slds-p-around_x-small">
        <template if:true={searchBarIsVisible}>
            <lightning-input
                label="Search Key"
                type="text"
                onchange={handleSearchKeyChange}
                class="search-bar"
            ></lightning-input>
        </template>
        <template if:true={products.data}>
            <template if:true={products.data.records.length}>
                <div class="content">
                    <template
                        for:each={products.data.records}
                        for:item="product"
                    >
                        <c-product-tile
                            key={product.Id}
                            product={product}
                            draggable={tilesAreDraggable}
                            onselected={handleProductSelected}
                            class="slds-m-around_x-small"
                        >
                        </c-product-tile>
                    </template>
                </div>
                <c-paginator
                    page-number={pageNumber}
                    page-size={products.data.pageSize}
                    total-item-count={products.data.totalItemCount}
                    onprevious={handlePreviousPage}
                    onnext={handleNextPage}
                ></c-paginator>
            </template>
            <template if:false={products.data.records.length}>
                <c-placeholder
                    message="There are no products matching your current selection"
                ></c-placeholder>
            </template>
        </template>
        <template if:true={products.error}>
            <c-error-panel errors={products.error}></c-error-panel>
        </template>
    </div>
</template>

And here is the JavaScript controller file:

import { LightningElement, api, wire } from 'lwc';

// Ligthning Message Service and message channels
import { publish, subscribe, MessageContext } from 'lightning/messageService';
import PRODUCTS_FILTERED_MESSAGE from '@salesforce/messageChannel/ProductsFiltered__c';
import PRODUCT_SELECTED_MESSAGE from '@salesforce/messageChannel/ProductSelected__c';

// getProducts() method in ProductController Apex class
import getProducts from '@salesforce/apex/ProductController.getProducts';

/**
 * Container component that loads and displays a list of Product__c records.
 */
export default class ProductTileList extends LightningElement {
    /**
     * Whether to display the search bar.
     * TODO - normalize value because it may come as a boolean, string or otherwise.
     */
    @api searchBarIsVisible = false;

    /**
     * Whether the product tiles are draggable.
     * TODO - normalize value because it may come as a boolean, string or otherwise.
     */
    @api tilesAreDraggable = false;

    /** Current page in the product list. */
    pageNumber = 1;

    /** The number of items on a page. */
    pageSize;

    /** The total number of items matching the selection. */
    totalItemCount = 0;

    /** JSON.stringified version of filters to pass to apex */
    filters = {};

    /** Load context for Ligthning Messaging Service */
    @wire(MessageContext) messageContext;

    /** Subscription for ProductsFiltered Ligthning message */
    productFilterSubscription;

    /**
     * Load the list of available products.
     */
    @wire(getProducts, { filters: '$filters', pageNumber: '$pageNumber' })
    products;

    connectedCallback() {
        // Subscribe to ProductsFiltered message
        this.productFilterSubscription = subscribe(
            this.messageContext,
            PRODUCTS_FILTERED_MESSAGE,
            (message) => this.handleFilterChange(message)
        );
    }

    handleProductSelected(event) {
        // Published ProductSelected message
        publish(this.messageContext, PRODUCT_SELECTED_MESSAGE, {
            productId: event.detail
        });
    }

    handleSearchKeyChange(event) {
        this.filters = {
            searchKey: event.target.value.toLowerCase()
        };
        this.pageNumber = 1;
    }

    handleFilterChange(message) {
        this.filters = { ...message.filters };
        this.pageNumber = 1;
    }

    handlePreviousPage() {
        this.pageNumber = this.pageNumber - 1;
    }

    handleNextPage() {
        this.pageNumber = this.pageNumber + 1;
    }
}

Notice that the searchBarIsVisible and titlesAreDraggable properties use the @api decorator and that the developer has kindly added a TODO comment here suggesting that the normalized value may come as a boolean, string or otherwise.

The reason the values may some across differently is because these two properties are configureable in the design file, but only for Record Pages and Community default pages. This means that anyone can use Lightning App Builder to change those values (well, at least for Record and Community Default pages).

To see how this is done, let’s take a look at the meta configuration file for this component:

<?xml version="1.0" encoding="UTF-8" ?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>49.0</apiVersion>
    <isExposed>true</isExposed>
    <masterLabel>Product Tile List</masterLabel>
    <targets>
        <target>lightning__AppPage</target>
        <target>lightning__RecordPage</target>
        <target>lightning__HomePage</target>
        <target>lightningCommunity__Page</target>
        <target>lightningCommunity__Default</target>
    </targets>
    <targetConfigs>
        <targetConfig targets="lightning__RecordPage">
            <property
                name="searchBarIsVisible"
                type="Boolean"
                label="Search bar visible"
            />
            <property
                name="tilesAreDraggable"
                type="Boolean"
                label="Product tiles are draggable"
            />
            <objects>
                <object>Order__c</object>
            </objects>
        </targetConfig>
        <targetConfig targets="lightningCommunity__Default">
            <property
                name="searchBarIsVisible"
                type="Boolean"
                label="Search bar visible"
            />
            <property
                name="tilesAreDraggable"
                type="Boolean"
                label="Product tiles are draggable"
            />
        </targetConfig>
    </targetConfigs>
</LightningComponentBundle>

Notice there are two targetConfig entries. One is for lightning__RecordPage and the other is for lightningCommunity__Default, but they both define the same properties. Even though these properties have default values of false, anyone assembling the pages for the two targeted page types can change these values in Lightning App Builder.

Consider this Change to the Configuration

Not to be too critical, but I can see room for improvement in the way the productTileList component was configured. After all, there is always room for improvement in anything we do, right?

The first change I would make is to add a description for all the targetConfig properties. The description is typically a short sentence, but it appears as a tooltip in Lightning App Builder. This sentence could be used to indicate to the app assembler that the property value should be either true or false and not any other value perhaps?

The end result would look something like the following (notice the tooltip on the right):

Lightning App Builder used to configure the productTileList component.
Lightning App Builder used to configure the productTileList component.

For more tips about what you can do to make your components configureable, check out this great developer doc page.

Moving to Lightning? Should you learn about Aura or LWC?

Image Source: SpringML

I have had a few people ask me this question and I think it is a good one, so I wanted to answer it in a post. First of all, for anyone not sure what the difference is between Aura and LWC, you might want to checkout this post I did.

As for the answer….wait for it…..It depends. It depends on:

  1. How experienced you as a Developer or Admin with JavaScript and HTML5. If you are very experienced and have even done work with React or Angular, then I would suggest starting with LWC’s.
  2. If you are not very experienced with modern JavaScript or HTML5, nor are you very experienced with modern development tools such as Visual Studio Code, Command Line Interfaces, GitHub, then I would start with Aura components. My latest Pluralsight course will get you up to speed on the new modern development tools that Salesforce offers. Once you feel comfortable suing these tools, then your transition to LWC’s will be much easier.
  3. Like Salesforce, I do think that LWC’s are the better alternative in terms of component performance, but if all you need to build are very simple components, then there is no reason why you cannot start with Aura and transition to LWC’s. To be entirely honest, LWC’s are more challenging to create for developers that do not fall into the first category I described.

I hope this post helps anyone not sure about which technology to approach first. Feel free to reach out to me if you have additional questions or concerns.

Sara

New Pluralsight Course: Getting Started Customizing Salesforce with Lightning Aura Components

I am very proud to announce the release of my latest Pluralsight course, “Customizing Salesforce with Lightning Aura Components“. This is actually a total re-write of my most popular course, “Customizing Salesforce with Lightning Components”, released in 2017. The key word change in the title is the addition of the word, “Aura“.

New Pluralsight Course, which includes working with VS Code and the CLI
New Pluralsight Course, which includes working with VS Code and the CLI

This course is different because at the time that course was released, Lightning Components were not called Lightning Aura Components AND most importantly, the new modern toolset known as Salesforce DX was not yet available. There were also no Lightning Web Components available yet.

I personally believe that for many developers, transitioning to LWC would be much easier if they were to first learn to build simple Aura components using the new modern tools first.

In this course, all those great shiny new tools will be used to build very simple Aura Lightning Components. The original course used the online Developer Console, which is what Trailhead still uses in all their content about Aura Components. On Trailhead, the new tools are only used for Lightning Web Components.

So, if you want to learn about building Aura Components, while also embracing the new modern toolset, this course is perfect for you. And if you want to learn about Lightning Web Components (LWC’s), then you can check out the “Building Your First Lightning Web Component (LWC) for Salesforce” course that I released earlier this year.

Either way, you will be knowledgeable in all the latest that Salesforce has to offer. I personally believe that for some developers, transitioning to LWC would be much easier if they were to first learn to build simple Aura components using the new modern tools first.

This course includes a GitHub Repo that features all the code covered in the course. The material covered includes the following:

Understanding the Lightning Component Framework

Refer to the following blog post #1 . In this post/Pluralsight module), you will learn:

  1. Who Should be Building Aura Components?
  2. Where Can Aura Components Be Used?
  3. Anatomy of an Aura Component Bundle
  4. Creating an Aura Component Bundle

Creating Aura Components with the Salesforce CLI

Refer to the following blog post # 2 .

  1. Using an Interactive Development Environment (IDE)
  2. Getting Setup with an IDE
  3. Understanding Salesforce DX (SFDX)
  4. Creating an Aura Component in Visual Studio (VS) Code
  5. Exposing Aura Components to Lightning App Builder

Working with Data

Refer to the following blog post # 3 .

  1. Working with Controllers
  2. Working with Apex and DML
  3. Creating a New Open Cases Component
  4. Using the CLI to Load Data to Scratch Org
  5. Enforcing Apex Security
  6. What About Caching and Usability?

Working with Record Forms

Refer to the following blog post # 4 .

  1. Working with Record Form Base Lightning Components
  2. Creating a New Quick Case Component
  3. Creating Quick Actions

Working with Salesforce Mobile

Refer to the following blog post # 5 .

  1. Working with the new Salesforce Mobile App
  2. Using the Salesforce Mobile App QuickStart Page
  3. Wrapping Up

Building Your First Lightning Web Component for Salesforce Pluralsight Course is Live!!!

I am so excited to finally announce that my new Pluralsight course, “Building Your First Lightning Web Component for Salesforce” course is Live!

New Pluralisght course "Building Your First Lightning Web Component for Salesforce"

The development of this course was a labor of love for me and I have to admit something I worked so hard on. I would not compromise the quality for anything (including the three deadlines I missed). Luckily, I was working with some of the best editors in the world (huge shout out to Bentley Lignell, Stacy Sohn and Austin Allen).

As for the course, it covers the following:

  • LWC Benefits and what are Web Components
  • Setting up you development environment with Salesforce, Visual Studio Code and Salesforce DX
  • Component Design using Custom DOM events
  • Working with Salesforce Data using the Wire Adapter and Debugging with Chrome Developer Tools
  • Converting Aura components and lessons I learned
  • Testing your JavaScript with Jest

In the course, we will be building an application that will look like this:

Lead Locator LWC app

If you are looking for the final code created in this course, you can find it here.

For anyone that might not be a Pluralsight subscriber yet, here is a link to a free 10-day trial.

And finally, please feel free to send me your feedback on the course. Good or bad. I appreciate it all because it helps me to develop better content that meets your needs.

Post 6 – Building Your First Lightning Web Component for Salesforce Series

This will be the sixth of a series of posts I will be doing over the next few weeks. They will all lead up to the introduction of my new course in January 2020 titled, “Building Your First Lightning Web Component for Salesforce” from Pluralsight. These posts will contain final code snippets of the code used in each course module, but will not include all the tips and best practices about using Visual Studio Code and SFDX, along with the way I personally approach teaching in my video courses.

Handling Testing and Wrapping Up

If you are already a Salesforce developer then you know that server-side Apex Unit tests are required, but client-side JavaScript tests are not and if you are like most developers, you probably think that creating tests should be optional, kind of like documenting, right?

You might not believe this, but writing tests (even for non-required JavaScript) can actually save you a lot of time, such as:

  • Time from chasing down bugs
  • Fixing those bugs
  • Releasing your fix using whatever release process your company uses

Introducing Jest

Jest is an easy to use tool that was originally created by the engineers at Facebook. It is now supported as open-source by the community. Salesforce DX provides a utility that lets you setup and configure Jest for your LWC project.

There are a few steps involved in getting this setup, which include:

  • Install prerequisites (which includes the latest versions of node.js and npm). Even if you already have node installed, it is recommended that you install the Long Term Support Version of Node.js, which will also include npm. This will guarantee that critical bugs will be fixed for a total of 30 months. You can install the LTS version from here.
  • Setup LWC VS Code project with npm – Go to the Terminal window in your VS Code project and from the root folder, run the following command:

npm init

This will create a package.json file. Just hit enter through all the prompts

  • Install sfdx-lwc-jest and dependencies – Also from Terminal window:

npm install

This will create a package-lock.json file

npm install @salesforce/sfdx-lwc-jest --save-dev

  • Identify test script – Open the package.json file and add this to the scripts section:
,
    "test:unit": "sfdx-lwc-jest --coverage",
    "test:unit:watch": "sfdx-lwc-jest --watch",
    "test:unit:debug": "sfdx-lwc-jest --debug"

Writing Jest Tests

Unlike Apex unit tests, Jest tests are run locally and are completely independent of Salesforce. As a best practice, you should create all your tests in a special folder at the root of your project that is named __tests__. If you look at the .forceignore file in your project, you will see that an entry for this folder already exists, thus excluding any tests from being pushed to your scratch org.

In the course, I walk you through creating a test of the leadList component. That test is just a JavaScript file that is created in the __tests__folder and is named: leadList.test.js (which is also a recommended best practice). In that folder, there is also a data subfolder that contains two JSON files named: getLeads.json and getNoLeads.json. Below is the code for the test:

import { createElement } from 'lwc';
import leadList from 'c/leadList';
import { registerApexTestWireAdapter } from '@salesforce/sfdx-lwc-jest';
import searchLeads from '@salesforce/apex/LeadSearchController.searchLeads';

// Represent a list of valid records
const mockGetLeads = require('./data/getLeads.json');
// Represent a list of no records
const mockGetNoLeads = require('./data/getNoLeads.json');
// Register the Apex wire adapter
const searchLeadsAdapter = registerApexTestWireAdapter(searchLeads);

describe('c-lead-list', () => {
    beforeAll(() => {
        // Use fake timers as setTimeout
        jest.useFakeTimers();
    });

    // Best practice to cleanup after each test
    afterEach(() => {
        // The jsdom instance is shared across test cases in a single file so reset the DOM
        while (document.body.firstChild) {
            document.body.removeChild(document.body.firstChild);
        }
    });

    describe('searchLeads @wire returns records with search parameter', () => {
        it('called with data from input', () => {
            const USER_INPUT = 'Bertha';
            const SEARCH_TERM = { searchTerm: USER_INPUT };

            // Create the leadList element
            const element = createElement('c-lead-list', {
                is: leadList
            });
            document.body.appendChild(element);

            // Simulate user input
            const inputValue = element.shadowRoot.querySelector('lightning-input');
            inputValue.value = USER_INPUT;
            inputValue.dispatchEvent(new CustomEvent('change'));

            // Run the fake timers
            jest.runAllTimers();

            // Return a promise to wait for asynchronous results
            // and fail if promise is rejected
            return Promise.resolve().then(() => {
                expect(searchLeadsAdapter.getLastConfig()).toEqual(
                    SEARCH_TERM
                );
            });
        });

        it('renders data of one record', () => {
            const USER_INPUT = 'Bertha Boxer';

            // Create the leadList element
            const element = createElement('c-lead-list', {
                is: leadList
            });
            document.body.appendChild(element);
            
            // Simulate user input
            const inputValue = element.shadowRoot.querySelector('lightning-input');
            inputValue.value = USER_INPUT;
            inputValue.dispatchEvent(new CustomEvent('change'));

            // Run the fake timers
            jest.runAllTimers();

            // Use the searchLeadsAdapter to emit data
            searchLeadsAdapter.emit(mockGetLeads);

            // Return a promise to wait for asynchronous results
            // and fail if promise is rejected
            return Promise.resolve().then(() => {
                // select some elements that would be rendered if it succeeded
                const detailElement = element.shadowRoot.querySelector('lightning-datatable');
                const rows = detailElement.data;
                expect(detailElement.length).toBe(searchLeadsAdapter.length);
                expect(rows[0].Name).toBe(mockGetLeads[0].Name);
            });

        });

        it('renders data when no record is available', () => {
            const USER_INPUT = '';

            // Create the leadList element
            const element = createElement('c-lead-list', {
                is: leadList
            });
            document.body.appendChild(element);

            // Simulate user input
            const inputValue = element.shadowRoot.querySelector('lightning-input');
            inputValue.value = USER_INPUT;
            inputValue.dispatchEvent(new CustomEvent('change'));

            // Run the fake timers
            jest.runAllTimers();

            // Use the searchLeadsAdapter to emit data
            searchLeadsAdapter.emit(mockGetNoLeads);

            // Return a promise to wait for asynchronous results
            // and fail if promise is rejected
            return Promise.resolve().then(() => {
                // select some elements that would be rendered if it succeeded
                const detailElement = element.shadowRoot.querySelector('lightning-datatable');
                expect(detailElement.length).toBe(searchLeadsAdapter.length);
            });
        });

    });

    describe('searchLeads @wire returns error', () => {
        it('shows error panel', () => {
            // Create the leadList element
            const element = createElement('c-lead-list', {
                is: leadList
            });
            document.body.appendChild(element);

            // Emit error from @wire
            searchLeadsAdapter.error();

            // Return a promise to wait for any asynchronous DOM updates. Jest
            // will automatically wait for the Promise chain to complete before
            // ending the test and fail the test if the promise rejects.
            return Promise.resolve().then(() => {
                const errorPanelEl = element.shadowRoot.querySelector(
                    'c-error-panel'
                );
                expect(errorPanelEl).not.toBeNull();
            });
        });
    });

})

And here are the two data files:

getLeads.json:

[
    {
        "Name": "Bertha Boxer",
        "Phone": "(850) 644-4200",
        "Company": "Farmers Coop. of Florida",
        "Title": "Director of Vendor Relations",
        "Street": "321 Westcott Building",
        "City": "Tallahassee",
        "State": "FL",
        "PostalCode": "32306"
    }
]

and getNoLeads.json:

[]

Running Jest Tests

To run the test seen above, you will need to return to the Terminal window and run this command:

npm run test:unit

Wrapping Up

So, this is the last post in the series and I should be announcing the release of this course soon. Sorry it has taken so long, but I hope you go away with these thoughts:

  •  Less frameworks; more web standards  
  • Embrace VS Code and Salesforce DX  
  • Use CustomEvents when possible  
  • LWC programming model is very different  
  • Never be embarrassed about mistakes  
  • Keep learning and share it

Post 5 – Building Your First Lightning Web Component for Salesforce Series

This will be the fifth of a series of posts I will be doing over the next few weeks. They will all lead up to the introduction of my new course in January 2020 titled, “Building Your First Lightning Web Component for Salesforce” from Pluralsight. These posts will contain final code snippets of the code used in each course module, but will not include all the tips and best practices about using Visual Studio Code and SFDX, along with the way I personally approach teaching in my video courses.

Converting Aura Components to Lightning Web Components

In this module, I will be sharing things I learned based on mistakes I made when I first converted an Aura app to LWC’s. This was the result of that attempt.

For a while I intended to use what I did in that first converted app for this course, but luckily for you, I did not. Instead, I contacted my friend Kevin Hill (@KevinJHill) at Salesforce and he volunteered to review my app. He politely suggested that I make a few changes and thank goodness I did.

The biggest mistake I made was not fully accepting this inevitable truth at first:

“The LWC programming model is fundamentally different than the Aura model”

That message is so important to your success with LWC’s, so here it is again:

I did finally accept this and therefore re-designed the solution I ended up using for this course. The version I will show in the course may look similar to the original version, but underneath it is different and if you watch the course you will even get to see performance results that demonstrate that.

The talented developers at Salesforce (which included Kevin Hill and many others), did an incredible job creating Lightning Web Components. If you attempt to just blindly convert an Aura app to an LWC without accepting that the programming model is different, then you will not get the performance advantages that it provides. And imho, that is like spitting in the face of those GREAT developers at Salesforce.

So, please do not do that. And I hope you watch my course when it is released soon. It is almost finished.

Post 4 – Building Your First Lightning Web Component for Salesforce Series

This will be the fourth of a series of posts I will be doing over the next few weeks. They will all lead up to the introduction of my new course in January 2020 titled, “Building Your First Lightning Web Component for Salesforce” from Pluralsight. These posts will contain final code snippets of the code used in each course module, but will not include all the tips and best practices about using Visual Studio Code and SFDX, along with the way I personally approach teaching in my video courses.

Working With Salesforce Data

The most important thing you need to be aware of when working with Salesforce Data is the Lightning Data Service, or LDS. If you have worked with Aura components, then you have probably already heard about it since it was introduced back in late 2017. The LDS is built on top of the UI or User Interface API. This is the API that Salesforce developers use internally to build the Lightning Experience.

The reason why has to do with the following benefits:

  1. Caching, so loading data is as fast as possible
  2. Progressive loading of data, which also improves performance
  3. Optimization of server calls, which guess what, offers performance advantages.

Hopefully, you are seeing the key factor here.

Using the Lightning Data Service Wire Adapter

As I mentioned in the last post, I am building this solution in stages. In the last post, I created a very simple child component named leadListItem to display all the leads returned by a search term. This was ok, but the output was definitely not pretty. The easiest way to fix that is to take advantage of one of the many LWC base components, which as of Dreamforce 2019, are now mostly available as open source.

And one of the best and easiest to use base components, lightning-datatable offers incredible benefits such as formatting for appropriate data types, header and row level actions, resizing of columns sorting and text wrapping. And there are even more.

I will be replacing the template code in leadList.html that called the leadListItem component, with a call to the lightning-datatable. The HTML will now look like this:

<template>
    <lightning-card title="Lead Search" icon-name="standard:search" class="slds--around_medium">
        <div class="slds-box slds-theme_default">
            <lightning-input
                label="Search Term"
                variant="label-hidden"
                placeholder="Search by name, phone, website, or address"
                type="text"
                value={searchTerm}
                onchange={handleSearchTermChange}>
            </lightning-input>
         </div>
        <div class="slds-m-around_small">
            <lightning-datatable
                key-field="id"
                data={leads}
                columns={cols}
                onrowaction={handleRowAction}
                hide-checkbox-column = "true">
            </lightning-datatable>
            <template if:true={error}>
                // Hint. In the actual course, I will implement something better than this
                The following error was encountered: {error}
            </template>
        </div>
    </lightning-card>
</template>

I will need to remove the JSON data in the JavaScript and add a constant to support the column definitions, as well as a new tracked variable for the columns and another for the error that could be returned and only rendered when there is an actual error using the if:true directive in the HTML template.

To help minimize the number of Apex calls, I will use a common technique that was implemented in the lwc-recipes repo. It involves slightly delaying the dispatch of the newsearch Custom Event.

I will also need to add a new imports command that is used to call Apex Code that returns results from a search. And, I will need to add another imports for the wire directive to the top import command. The wire directive is used to read Salesforce data reactively and when the data is provisioned, it automatically re-renders the component. This will be implemented in loadLeads.

And finally, there will be a handler that uses the navigation service to navigate the user to the Lead record page if they decide to click the info icon displayed in the last datatable column. The code for the leadList.js will now look as follows:

import { LightningElement, track, wire } from 'lwc';
import searchLeads from '@salesforce/apex/LeadSearchController.searchLeads';
import { NavigationMixin } from 'lightning/navigation';

/** The delay used when debouncing event handlers before a method call. */
// This code was copied directly from the trailheadapps/lwc-recipes GitHub repo
const DELAY = 350;

// It is a general convention to capitalize constants
const COLS = [
    {
        label: 'Name',
        fieldName: 'Name',
        type: 'text'
    },
    {
        label: 'Title',
        fieldName: 'Title',
        type: 'text'
    },
    {
        label: 'Company',
        fieldName: 'Company',
        type: 'text'
    },
    {
        label: 'View',
        type: 'button-icon',
        initialWidth: 75,
        typeAttributes: {
            title: 'View Details',
            alternativeText: 'View Details',
            iconName: 'action:info'

        }
    }
];

export default class LeadList extends NavigationMixin(LightningElement) {
    @track leads = [];
    @track searchTerm;
    @track cols = COLS;
    @track error;
    
    handleSearchTermChange(event) {
        this.searchTerm = event.target.value;
        if (this.leads) {
            const selectedEvent = new CustomEvent('newsearch', {detail: this.searchTerm});
            window.clearTimeout(this.delayTimeout);
            // eslint-disable-next-line @lwc/lwc/no-async-operation
            this.delayTimeout = setTimeout(() => {
                this.dispatchEvent(selectedEvent);
            }, DELAY);
        }
    }

    @wire(searchLeads, {
        searchTerm: '$searchTerm'
    })
    loadLeads({ error, data }) {
        if (data) {
            this.leads = data;
            this.error = undefined;
        } else if (error) {
            this.error = error;
            this.leads = undefined;
        }
    }

    handleRowAction(event) {
        const row = event.detail.row;
        this.record = row;
        this[NavigationMixin.Navigate]({
            type: 'standard__recordPage',
            attributes: {
                recordId: row.Id,
                actionName: 'view',
            },
        });
    }

}

The last thing to do is to create the Apex class that will do the actual search of the Salesforce data. It will use a SOSL query to perform a special kind of search across multiple fields in a single object. Notice that the searchLeads method includes an @AuraEnabled tag, as well as the cacheable = true. This ensures the data returned will be cached, which is perfect for mutable data such as the lead addresses. The code for that will look as follows:

public with sharing class LeadSearchController {
    
    @AuraEnabled(cacheable=true)
    public static List<Lead> searchLeads( String searchTerm ) {
        List<Lead> leads = new List<Lead>();
        if ( String.isNotBlank( searchTerm ) ) {
            List<List<SObject>> searchResults = [
                FIND :searchTerm
                RETURNING Lead(
                    Id, Name, Title, Company,
                    Street, City,
                    State, PostalCode
                    ORDER BY Name
                    LIMIT 10
                )
            ];
            leads = searchResults[0];
        }
        return leads;
    }
}

Using the CLI to Load Data

At this point, if you were to save and push all this code to your scratch org and then open the Lead Locator tab and do a search, what do you think would happen?

Well, you should get nothing returned because by default scratch orgs are not loaded with any Lead data. But, we can use some very handy CLI commands to export data from another developer org and then import it into our scratch org as JSON data.

I have already used the export command to get 10 lead records from a developer org. That command looked like the following:

sfdx force:data:tree:export --query \                                                                	"SELECT ID, FirstName, LastName, Phone, Company, Title, Street, City, \ 		State, PostalCode FROM Lead limit 10" \                                                	
--outputdir ./data --json

And to import them, I will only need to use the terminal in Visual Studio Code to execute another CLI command that looks like this:

sfdx force:data:tree:import -f data/Lead.json -u <your scratch org username here>

Adding the Lead Map Component

To finish off the solution, I will need to add the leadMap component. It will use another very useful Lightning base component called lightning-map. That base component can display multiple locations using geocoding and mapping imagery from Google Maps. The leadList.html for that component will look like this:

<template>
    <template if:true={leads}>
        <lightning-map
                zoom-level="11"
                map-markers={markers}
                markers-title="Leads">
        </lightning-map>
    </template>
    <template if:true={error}>
            // Hint. In the actual course, I will implement something better than this
            The following error was encountered: {error}
    </template>
</template>

The leadList.js file will import the same LeadSearchController code used in the leadList, but remember that this data is cacheable. And the @wire directive will do a search using a new variable called searchInput. This variable will be passed to the leadMap component as a public variable from the owner component using the @api directive. The final code for leadList.js will look like this:

import { LightningElement, track, wire, api } from 'lwc';
import searchLeads from '@salesforce/apex/LeadSearchController.searchLeads';

export default class leadMap extends LightningElement {
    @track markers = [];
    @track error;
    @track leads;

    // Private variable
    searchTerm;

    @api get searchInput() {
        return this.searchTerm;
    }

    set searchInput(value) {
        this.searchTerm = value;
    }

    @wire(searchLeads, {
       searchTerm: '$searchInput'
    })
    loadLeads({ error, data }) {
        if (data) {
            this.leads = data;
            this.markers = data.map(lead => {
                return {
                    location: {
                        Street: lead.Street,
                        State: lead.State,
                        City: lead.City,
                        PostalCode: lead.PostalCode
                    },
                    title: JSON.stringify(lead.Name),
                    icon: 'utility:pinned'
                }
            })
            this.error = undefined;
        } else if (error) {
            this.error = error;
            this.leads = undefined;
            this.markers = [];
        }
    }
}

The last thing to change is code in the Owner component. In the myFirstLWC.html, I will need to replace the placeholder text with the a call to the new leadList child component. But, to prevent the map from being displayed when the user first loads the page, I will need to use another CustomEvent called searchcomplete. If no search has been done, then the user will get a helpful info message displayed. The HTML code for the owner will now look like this:

<template>
    <div class="c-container">
            <lightning-layout multiple-rows="true">
                <lightning-layout-item padding="around-small" size="12">
                    <lightning-layout>
                        <lightning-layout-item padding="around-small" size="6">
                            <div class="slds-box slds-theme_default">
                                <c-lead-list onnewsearch={handleNewSearch}
                                onsearchcomplete={handleSearchComplete}></c-lead-list>
                            </div>
                        </lightning-layout-item>
                        <lightning-layout-item padding="around-small" size="6">
                            <div class="slds-box slds-theme_default">
                                    <lightning-card title="Lead Map" icon-name="action:map" class="slds--around_medium">
                                        <template if:true={searchComplete}>
                                            <c-lead-map search-input={searchInput}></c-lead-map>
                                        </template> 
                                        <template if:false={searchComplete}>
                                            <div class="slds-notify slds-notify_alert slds-theme_alert-texture slds-theme_info" role="alert">
                                                <span class="slds-assistive-text">info</span>
                                                <h2>You will need to enter a search term before a map is displayed.</h2>
                                            </div>
                                        </template> 
                                    </lightning-card>  
                                </div>
                        </lightning-layout-item>    
                    </lightning-layout>
                </lightning-layout-item>
            </lightning-layout>
        </div>
</template>

Of course, this will mean I will also need to modify the myFirstLWC.js file to include a handler for that new CustomEvent. This code will now look as follows:

import { LightningElement, track } from 'lwc';

export default class MyFirstLWC extends LightningElement {
    @track searchTerm;
    @track searchInput;
    @track searchComplete = false;

    handleNewSearch(event) {
        this.searchTerm = event.target.value; 
    }

    handleSearchComplete(event) {
        this.searchInput = event.detail;
        this.searchComplete = true;
    }
}

And to explain all this in terms of the component communication, consider the following diagram:

But, the course is not done, and the best is yet to come in the next post in which I will go over converting Aura components using an example similar to this with a completely different approach that I think you will find very interesting.

And finally, if you are super excited about this, you might want to consider taking advantage of this limited time offer from Pluralsight for 40% off an annual subscription until December 6, 2019.

40% Off Pluralsight – Last Chance to Save