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
- Fork
bdovhan/SimpleDataTableApp
application implemented using the Lightning Aura Framework.
- Perform a Git clone of your fork of SimpleDataTableApp to your local folder, for example, to
D:/Git/AuraToLWCWorkshop
.
- Read the App description and default available bat commands in
SimpleDataTable/readme.md
- Ensure that you have the default dev hub set up correctly, if not then set up devhub first.
- Run
Start.bat
(would work for Windows; need to implement .sh correspondent file for Mac).
- Wait until the setup script is executed successfully. Confirm that
SimpleDataApp.app
is opened.
Create a blank app and blank lightning web components
- Open the
SimpleDataTableApp Folder
, as a project in Visual Studio Code.
- 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.
- Copy the content of
SimpleDataTableApp App
to SimpleAppUsesLWC
.
- Create a blank
simpleEmployees
Lightning Web component. You may copy to it the commented code from SimpleEmployeeList
.
- Create a blank
simpleTable
Lightning Web component. You may copy to it the commented code from SimpleDataTable
.
Start converting SimpleDataTable to simpleTable
- 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.
- 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>
- 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() {}
}
- 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() {}
}
- Deploy your changes by the menu items or by the command
sfdx force:source:deploy -m LightningComponentBundle
Use Imperative Apex in simpleTable
- Call the Apex method imperatively. Create the method connectedCallback and inside of it, call the promise
getColumnsAndData
.
- Pass parameters
sObjectName
, sObjectFieldsNames
, whereClause into the promise initializer.
- Remove single quotes;
replace component.get('v.
with this ..
- Declare the public reactive properties
sObjectName
, sObjectFieldsNames
, whereClause
, and import the api
module
- The imperative Apex function is a promise. Write the arrow-function for the
then
callback, to process a successful execution.
- 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
- Change the colons to dashes in the commented code.
- 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>
- Use the
simpleEmployees
component inside of the SimpleAppUsesLWC
. The inner content of the SimpleAppUsesLWC
should look like:
<c:FakeOpportunityData/>
<c:simpleEmployees/>
- Deploy changes either by menu items or by the command line.
Error handling and troubleshooting
- 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'"/>
- Notice the two null-pointer errors.
- 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;
});
}
- Use Javascript Debugging if you don’t see the error displayed.
- 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 {}
- 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
- Notice that the sorting is not working in the current LWC version but that it is working in the Aura version.
- Copy methods
updateColumnSorting
, sortData
and sortBy
from SimpleDataTableHelper.js
into simpleTable.js
and then comment them out.
- Remove the
function
keyword and colon from the sortData
and sortBy
function definitions.
- 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.
- Replace
getParam('
with detail.
and remove the corresponding closing parenthesis.
- Replace
helper
with this
and replace the cmp
param with e.srcElement.
- Uncomment the
sortBy
code, as it contains a valid LWC code and doesn’t have to be converted.
- 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.
- 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
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.