Aura to Lightning Web Components: How it works in real life

I’ve been diving deep into migration from Aura to Lightning Web Components for quite some time now. When preparing for a workshop, to demonstrate how this can be done in action, I opted for the Aura component described in my article, which was used to build a simple employee list application using the standard lightning:datatable component from the Lightning Aura Framework. Since this standard component is also available for use in the Lightning Web Components Framework, I decided to prepare a workshop where the main task was to clone that application using an Aura component and replace the SimpleEmployeeList aura component with the SimpleEmployees Lightning Web Component counterpart. Also, I offered an additional task to clone another advanced application and replace a reference to another DataTable Aura component with the Lightning Web Component counterpart. Here, I’ll present a plan on how this migration can be performed with video screenshots of the most important steps.

The workshop task was to clone the SimpleDataApp and replace the SimpleEmployeeList Aura Component with the SimpleEmployees Lightning Web Component counterpart.

 

Setup a scratch org with prebuilt Aura components

  1. Fork bdovhan/SimpleDataTableApp application implemented using the Lightning Aura Framework.
  2. Perform a Git clone of your fork of SimpleDataTableApp to your local folder, for example, to D:/Git/AuraToLWCWorkshop.
  3. Read the App description and default available bat commands in SimpleDataTable/readme.md
  4. Ensure that you have the default dev hub set up correctly, if not then set up devhub first.
  5. Run  Start.bat (would work for Windows; need to implement .sh correspondent file for Mac).
  6. Wait until the setup script is executed successfully. Confirm that SimpleDataApp.app is opened.

    Setup a scratch org with prebuilt Aura components

Create a blank app and blank Lightning Web Components

  1. Open the SimpleDataTableApp Folder, as a project in Visual Studio Code.
  2. Create a clone of SimpleDataTableApp either in VS by menu item or by code, or in the Developer Console. Call it SimpleAppUsesLWC. If any component or app is created outside of the VS Code, use a retrieve menu item or command sfdx force:source:retrieve -m AuraDefinitionBundle to see that component or app inside the VS Code.
  3. Copy the content of SimpleDataTableApp App to SimpleAppUsesLWC.
  4. Create a blank simpleEmployees Lightning Web component. You may copy to it the commented code from SimpleEmployeeList.
  5. Create a blank simpleTable Lightning Web component. You may copy to it the commented code from SimpleDataTable.
    Create a blank app and blank Lightning Web Components

Start converting SimpleDataTable to simpleTable

  1. Change colons to dashes in the commented code; remove most double quotation marks (except for the inline string values like “Id” for keyField value) and standard provider references “!v” and “!c” from the commented code.
  2. Change camelCased properties to kebab-cased; add closing tags. The inner content of the html file of the component should look like the following:
        <lightning-datatable data={data} columns={columns} key-field="id" 
        hide-checkbox-column onsort={updateColumnSorting}>
        </lightning-datatable>
    
  3. Go to simpleTable.js and declare data and columns properties there and the updateColumnSorting method. Add track to imports. Now simpleTable.js should look like the following:
    	import { LightningElement, track } from 'lwc';
    	export default class SimpleTable extends LightningElement {
    		@track data;
    		@track columns;
    		
    		updateColumnSorting() {}
    	}
    
  4. Import the apex controller by inserting a line, like:  import getColumnsAndData from '@salesforce/apex/SimpleDataTableController.getColumnsAndData
    Now simpleTable.js should look like:

    	import { LightningElement, track } from 'lwc';
    	import getColumnsAndData from '@salesforce/apex/SimpleDataTableController.getColumnsAndData';
    	export default class SimpleTable extends LightningElement {
    		@track data;
    		@track columns;
    		
    		updateColumnSorting() {}
    	}
    
  5. Deploy your changes by the menu items or by the command sfdx force:source:deploy -m LightningComponentBundle

Use Imperative Apex in simpleTable

  1. Call the Apex method imperatively. Create the method connectedCallback and inside of it, call the promise getColumnsAndData.
  2. Pass parameters sObjectName, sObjectFieldsNames, whereClause into the promise initializer.
  3. Remove single quotes; replace component.get('v. with this ..
  4. Declare the public reactive properties sObjectName, sObjectFieldsNames, whereClause, and import the api module
  5. The imperative Apex function is a promise. Write the arrow-function for the then callback, to process a successful execution.
  6. Write the arrow-function for the error callback, add the code to display the error and add the private reactive error property @track error; to the Javascript module.

        <template if:true={error}>
            <template if:true={error.body}>
                {error.body.message}
            </template>
        </template>
    

Now the list of attributes in it should look like the following:

    @api sObjectName;
    @api sObjectFieldsNames;
    @api whereClause;

    @track data;
    @track columns;
    @track error;

The connected callback function should look like this:

    connectedCallback() {
        getColumnsAndData({
            sObjectName: this.sObjectName,
            sObjectFieldsNames: this.sObjectFieldsNames,
            whereClause: this.whereClause
        }).then(result=>{
            this.data = result.data;
            this.columns = result.columns;
        }).catch(error=>{
            this.error = error;
        });
    }

 

Convert SimpleEmployeeList to simpleEmployees

  1. Change the colons to dashes in the commented code.
  2. Change camelCased properties to kebab-cased. Add closing tags.
    The inner content of the Html file, of the component, should look like the following:

        <c-simple-table s-object-name="Contact"
        s-object-fields-names="FirstName,LastName,BirthDate,HireDate__c,Branch__c,Position__c,Email,Phone"
        where-clause="RecordType.Name = 'Employee'">
        </c-simple-table>
    
  3. Use the simpleEmployees component inside of the SimpleAppUsesLWC. The inner content of the SimpleAppUsesLWC should look like:
        <c:FakeOpportunityData/>
        <c:simpleEmployees/>
    
  4. Deploy changes either by menu items or by the command line.

Error handling and troubleshooting

  1. Add code to SimpleAppUsesLWC in order to display an error, because of an invalid object name.
       <c:simpleTable sObjectName="Contact1"
        sObjectFieldsNames="FirstName,LastName,BirthDate,HireDate__c,Branch__c,Position__c,Email,Phone"
        whereClause="RecordType.Name = 'Employee'"/>
    
  2. Notice the two null-pointer errors.
  3. Change the code of simpleTable.js to transform the string value into an array and fix one of the null pointer errors.
   connectedCallback() {
        getColumnsAndData({
            sObjectName: this.sObjectName,
            sObjectFieldsNames: this.sObjectFieldsNames.split(','),
            whereClause: this.whereClause
        }).then(result=>{
            this.data = result.data;
            this.columns = result.columns;
        }).catch(error=>{
            this.error = error;
        });
    }
  1. Use Javascript Debugging if you don’t see the error displayed.
  2. Notice the null-pointer error and learn how to transform it into a meaningful exception by inserting a piece of code to the SchemaProvider class before line #6.
               if ( Type.forName(token) == null ) {
                    throw new CustomException(token + ' is not valid SObject name');
                }
    

    And introduce an inner CustomException class at the bottom of the class

    public class CustomException extends Exception {} 
  3. Comment a catch block in the SimpleDataTableController lines 41 and 43-45, and see that the unhandled exceptions result in non-readable errors. Notice the non-readable exception and then revert change in the SimpleDataTableController.

Fix sorting by converting the updateColumnSorting method into the Javascript module method

  1. Notice that the sorting is not working in the current LWC version but that it is working in the Aura version.
  2. Copy methods updateColumnSorting, sortData and sortBy from SimpleDataTableHelper.js into simpleTable.js and then comment them out.
  3. Remove the function keyword and colon from the sortData and sortBy function definitions.
  4. Replace event by e, then replace getSource() by srcElement, and remove set("v. and the corresponding closing parenthesis. Also replace the comma by an assignment operator.
  5. Replace getParam(' with detail. and remove the corresponding closing parenthesis.
  6. Replace helper with this and replace the cmp param with e.srcElement.
  7. Uncomment the sortBy code, as it contains a valid LWC code and doesn’t have to be converted.
  8. Inside sortData, replace cmp with src, and replace get("v.data") with JSON.parse(JSON.stringify(src.data)) and cmp.set("v.data", with src.data = and remove the corresponding closing parenthesis.
  9. Deploy changes and confirm that the sorting now works in the LWC version. Check to ensure the code of these functions looks like:
updateColumnSorting(e) {
        e.srcElement.sortedBy = e.detail.fieldName;
        e.srcElement.sortedDirection = e.detail.sortDirection;
        this.sortData(e.srcElement, e.detail.fieldName, e.detail.sortDirection);
    }
    
    sortData(src, fieldName, sortDirection) {
        /// src = equivalent to event.getSource()
        var data = JSON.parse(JSON.stringify(src.data));
        var reverse = sortDirection !== 'asc';
        //sorts the rows based on the column header that's clicked
        var primer = (data && data.length && data[0].Origin) ? (x, field)=>x.Origin[field] : null;
        data.sort(this.sortBy(fieldName, reverse, primer));
        src.data = data;
         
    }
    sortBy(field, reverse, primer) {
        var key = primer ?
            function(x) {return primer(x, field)} :
            function(x) {return x[field]};
        //checks if the two rows should switch places
        reverse = !reverse ? 1 : -1;
        return function (a, b) {
            return a = key(a), b = key(b), reverse * ((a > b) - (b > a));
        }
    }

 

Implement the wired function version of getColumnsAndData call in simpleDataTableWiredFunction.js

Listing of code inside simpleDataTableWiredFunction.html

<template>
    <template if:true={error}>
        <template if:true={error.body}>
            {error.body.message}
        </template>
    </template>
    <lightning-datatable data={data} columns={columns} key-field="id"  hide-checkbox-column 
    onsort={updateColumnSorting}></lightning-datatable>
    <lightning-datatable data={data} columns={columns} key-field="id"  hide-checkbox-column 
        onsort={updateColumnSorting}></lightning-datatable>
</template>

Listing of code inside simpleDataTableWiredProperty.js

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

import getColumnsAndData from '@salesforce/apex/SimpleDataTableController.getColumnsAndData';
import {copy} from 'c/copy';
export default class SimpleDataTableWiredFunction extends LightningElement {
    @api sObjectName;
    @track sObjectFieldsNamesArray;
    get sObjectFieldsNames() {
        return this.sObjectFieldsNamesArray;
    }
    @api
    set sObjectFieldsNames(value) {
        this.sObjectFieldsNamesArray = value.split(',');
     }
    @api whereClause;

    @track data;
    @track columns;
    @track error;

    @wire(getColumnsAndData, {
        sObjectName: '$sObjectName', sObjectFieldsNames: '$sObjectFieldsNames'
        , whereClause: '$whereClause'
    })
    wiredGet({ error, data }) {
        if (data) {
            this.data = data.data;
            this.columns = data.columns;
            this.error = undefined;
        } else if (error) {
            this.error = error;
            this.data = null;
            this.columns = null;
        }
    }
	....

}

Note that in this case we have defined a private internal property to store fields as an array, and in public property setter we split the string value provided by a comma.

 

Implement the wired property version of getColumnsAndData and call in simpleDataTableWiredProperty

Listing of code inside simpleDataTableWiredProperty.html

<template>
    <template if:true={columnsAndData.error}>
        <template if:true={columnsAndData.error.body}>
            {error.body.message}
        </template>
    </template>
    <h1>Wired Property version</h1>
    <template if:true={columnsAndData.data}>
        <lightning-datatable data={columnsAndData.data.data} columns={columnsAndData.data.columns} key-field="id"  
        hide-checkbox-column onsort={updateColumnSorting}></lightning-datatable>
    </template>
</template>

Listing of code inside simpleDataTableWiredProperty.js

import { LightningElement, track, api, wire } from 'lwc';
import getColumnsAndData from '@salesforce/apex/SimpleDataTableController.getColumnsAndData';
import {copy} from 'c/copy';

export default class SimpleDataTableWiredProperty extends LightningElement {
    @api sObjectName;
    @track sObjectFieldsNamesArray;
    get sObjectFieldsNames() {
        return this.sObjectFieldsNamesArray;
    }
    @api
    set sObjectFieldsNames(value) {
        this.sObjectFieldsNamesArray = value.split(',');
     }
    @api whereClause;

    @wire(getColumnsAndData, {
        sObjectName: '$sObjectName', sObjectFieldsNames: '$sObjectFieldsNames'
        , whereClause: '$whereClause'
    })
    columnsAndData;
	... 
	
}

Note that in this case we also have defined the same private internal property to store fields as array declared for the wired function example, however, the code assigning value to the property is much shorter.

 

Clone DataTableTestApp and convert the cloned version for use in the LWC version of the DataTable Aura Component.

Listing of file dataTableLWC.html

<template>
    <template if:true={error}>
        <template if:true={error.body}>
            {error.body.message}
        </template>
    </template>
    <lightning-datatable data={data} columns={columns} key-field="id" sorted-by={sortedBy} 
        sorted-direction={sortDirection} hide-checkbox-column onsort={updateColumnSorting}></lightning-datatable>
</template>

Listing of file dataTableLWC.js

import { api } from 'lwc';
import getColumnsAndData from '@salesforce/apex/DataTableController.getColumnsAndData';
import SimpleDataTableLWC from 'c/simpleDataTableLWC';
export default class DataTableLWC extends SimpleDataTableLWC {
    @api overrides;
    @api valueModifiers;

    connectedCallback() {
        getColumnsAndData({ sObjectName:this.sObjectName, sObjectFieldsNames:this.sObjectFieldsNames.split(',')
            , whereClause: this.whereClause, overrides: JSON.parse(this.overrides.replace(/'/g, '"'))
            , valueModifiers: JSON.parse(this.valueModifiers.replace(/'/g, '"')) })
        .then(result=>{
            this.data = result.data;
            this.columns = result.columns;
        }).catch(error => {
            this.error = error;
        });
    }
}

Note that we are able to extend not only standard components but also custom components in order to use inheritance to reduce code duplication. Otherwise, we might end up with dataTableLWC javascript code almost completely duplicating simpleDataTableLWC javascript code.

Also, we have defined two public properties with @api decorator.

 

Retrieve all modified source metadata and commit those changes into your repository and dispose of scratch org.

Run ./finish.bat.

See this in action in the video screenshot

Retrieve all modified source metadata

In my case, I had already pulled and committed all my changes. So pull command didn’t find any more changed files in the case shown in video screenshot.

 

Conclusion

Salesforce workshops are a great way to explore new features, develop your skills and share personal experience with the tech community, whether you attend or organise one. During my last workshop I demonstrated migration from Aura to Lightning Web Components in action. Attendees practiced cloning the sample project build on Aura Framework and replacing the sample Aura Component with the Lightning Web Component counterpart.

Lightning Web Components help Salesforce Developers to follow the latest Javascript code standards. Every Salesforce developer should get to know the new features provided by Salesforce and suggest the best appropriate development option to customers.

Get help with Salesforce Lightning
Start a conversation. Use the contact form below and we’ll get back to you shortly.
Back to overview