LWC Table Sort – With Example!

Lightning-Web-Component_Table_Sort
Imagine sorting this pile!

Let’s do some coding! Lightning Web Components offer a standard table called the lightning-datatable. This component has a lot of built in functions, including sorting. But what if you need a custom table and you need it to sort? I’ll show you how to implement LWC table sort. The table itself will be HTML and it will pass values back to the controller, where the sorting will be done in Javascript. You can also use these techniques outside of Salesforce since we are using pure HTML and Javascript to achieve this.

Create The Initial Component

First, create a Lightning Web Component and put it on a page. You can follow the steps in my previous post to do so. Once you have the component created, copy the following code into the HTML component to create a very basic table:

<template>
    <div>
        <table>
            <thead>
                <tr>                     
                    <th>
                        <a onclick="{sort}">
                            <div>Name</div>
                        </a>
                    </th>
                    <th>
                        <a onclick="{sort}">
                            <div>Age</div>
                        </a>
                    </th>
                    <th>
                        <a onclick="{sort}">
                            <div>Occupation</div>
                        </a>
                    </th>
                    <th>
                        <a onclick="{sort}">
                            <div>Favorite Food</div>
                        </a>                        
                    </th>
                </tr>
            </thead>
            <tbody>
                <template for:each="{People}" for:item="person">
                    <tr key="{person.Id}">                
                        <td key="{person.Id}">                                
                            {person.Name}
                        </td>
                        <td key="{person.Id}">
                            {person.Age}
                        </td>
                        <td key="{person.Id}">
                            {person.Occupation}
                        </td>
                        <td key="{person.Id}">
                            {person.FavoriteFood}
                        </td> 
                    </tr>                                                                     
                </template>
            </tbody>
        </table>
    </div>
</template>

Place this code wholesale into your HTML. This will display a table of people with the following datapoints: their name, age, occupation, and favorite food. I will have another post in the future that goes into detail on HTML table tags and what they do but, for now, just use this code for this exercise. In your Javascript file, paste the following code in the curly braces following ‘LightningElement’:

    @api People = [
        {
            Id: 1,
            Name: 'John Smith',
            Age: 21,
            Occupation: 'Farmer',
            FavoriteFood: 'Corn',                          
        },
        {
            Id: 2,
            Name: 'Michael Jones',
            Age: 35,
            Occupation: 'Banker',
            FavoriteFood: 'Sandwiches',             
        },
        {
            Id: 3,
            Name: 'Mary Larson',
            Age: 18,
            Occupation: 'Teacher',
            FavoriteFood: 'Pizza',             
        },
    ]

This code is creating the data array to feed into the table. The array is called ‘People’ and this name is referenced in the HTML portion of the component, on Line 29:

<template for:each={People} for:item="person">

Salesforce supports iterating through an array in HTML, through the syntax above. This way, we just tell the HTML what array we want to display, in this case People, and what each row will be in that array, in this person. You can name the row variable whatever you want, as long as it is in the for:item quotation marks. Save these two pieces of code and deploy this to your org. Let’s see what it looks like!

Lightning Web Component Table Made From Salesforce Records

A simple table of data! Now, lets make it a bit easier to read and then we can add the sorting. Replace Line 3 with this:

<table class='slds-table'>

And reload the page:

Lightning Web Component Table

There, that looks a bit better. slds-table is a class Salesforce supports that adds a bit of styling to a stable to make it a little more user friendly.

Table Header Click Event

Now that we have our table, let’s talk about sorting. You’ll notice that the headers for each column are hyperlinks and they are clickable. Hover your mouse over them to see the icon change from a pointer to a hand. In the HTML, the headers were wrapped in <a> tags, which designate hyperlinks. you’ll also notice that in the <a> tag for each header, we have the line:

onclick={sort}

This is a javascript event that is fired off when the object in this tag is clicked. The object, in this example, is whatever the header text is. Javascript has many events, with most of the ones used in web development starting with “on.” Javascript has onclick, onkeypress, etc., and we will use more of these in the future. For now, we have an onclick definition and the word ‘sort.’ Whatever is in the curly braces is the name of the function you are going to run when the on click event happens. Now, let’s create the sort method in our controller so sort actually does something:

    sort(e) {
    }  

Add the above to your Javascript controller. The e that is being passed in is the event information from the event. In this case, the e here is the information for the on click event. Javascript automatically creates this event for us and passes it in the method when we used the onclick={sort} syntax. With this method, we can now sort!

Implementing Lightning Web Component Table Sort

In the sort method, we need to go through the array of data we created, People, and sort by one of the columns in that array. How do we know what column someone clicked on? We have to pass it in! This is done through the event information that is being passed in. With HTML and Javascript, you can pass in data from events that you configure. In this case, we want to know what column we pass in. We need to add the line:

data-id="[column name]" 

to our <a> tags. You can pass in any information from HTML to an event by calling it:

data-[variableName]="[variableValue]"

What this does is it passes that data into the events dataset, and you can then access it in the controller. Let’s take a look. Add the data-id=”[Header Name]” to each <a> tag in our HTML:

<template>
    <div>
        <table class='slds-table'>
            <thead>
                <tr>                     
                    <th>
                        <a data-id="Name" onclick={sort}>
                            <div>Name</div>
                        </a>
                    </th>
                    <th>
                        <a data-id="Age" onclick={sort}>
                            <div>Age</div>
                        </a>
                    </th>
                    <th>
                        <a data-id="Occupation" onclick={sort}>
                            <div>Occupation</div>
                        </a>
                    </th>
                    <th>
                        <a data-id="FavoriteFood" onclick={sort}>
                            <div>Favorite Food</div>
                        </a>                        
                    </th>
                </tr>
            </thead>
            <tbody>
                <template for:each={People} for:item="person">
                    <tr key={person.Id}>                
                        <td key={person.Id}>                                
                            {person.Name}
                        </td>
                        <td key={person.Id}>
                            {person.Age}
                        </td>
                        <td key={person.Id}>
                            {person.Occupation}
                        </td>
                        <td key={person.Id}>
                            {person.FavoriteFood}
                        </td> 
                    </tr>                                                                     
                </template>
            </tbody>
        </table>
    </div>
</template>

In the sort method, we can access this value using:

e.currentTarget.dataset.id;

And that’s it! You now know what column to sort on. Now let’s do the sort. We know what column we are sorting on, so we use Javascript’s array sort method to sort our People array by the column we click on. The array sort method looks like this:

    sort(e) {
        let table = JSON.parse(JSON.stringify(this.People));
        table.sort((a,b) => 
          {return a[e.currentTarget.dataset.id] > b[e.currentTarget.dataset.id] ? 1 : -1}
        );
        this.People = table;
    }   

Sort Method

We’ve now achieved LWC table sort, but there’s a lot going on here; let’s break down the method. First, this.People needs to be written to a local variable called table, so we can call the function sort() on it. this.People is a JSON object, so we use standard methods to decode it. Now that we have our variable table, we call sort() on table. Sort() takes in a function as its parameter. We can decide this function as we see fit, but it will be a function that compares two values and determines which one has priority in the sort over the other. Let’s look at the syntax in more detail. Our function is:

(a,b) => 
          {return a[e.currentTarget.dataset.id] > b[e.currentTarget.dataset.id] ? 1 : -1}

What this is saying is, take in two items in the list, a and b, and compare if a.[Column Name] is greater than b.[Column Name]. If a is greater, return a 1. If not, return -1. That is enough for the sort method to properly determine the order. You can see we are using a ternary operator here, which has the below syntax:

[condition] ? result if true : result if false 

Also, [e.currentTarget.dataset.id] is in brackets because it is being used as a property. This is bracket notation and Javascript needs this for us to access the property of the header value we want. Now, deploy this and try clicking on the headers of our table:

Lightning Web Component Table Sort

It sorts! I clicked on Age in this case, but try it on any of the columns. And that’s it! Now, this sorts, but it’s a little clunky. First, you can’t sort in the other order. Second, there’s no indicator that of sorting being done. Let’s correct both of these issues now.

Switching Sort Order

To get the sort order to switch to descending order, we have to reverse our function in certain cases. To do this, let’s think about how any website sorts. If you click on the same column, we sort in the opposite direction. Clicking on a new column should reset the sort to go in ascending order. To do this, let’s create a variable called sortDirection and a variable called sortColumn. This way, we can store what column and direction came last:

    sortedDirection = 'asc';
    sortedColumn;

By default, sortedDirection should start in ascending order. Let’s add the logic to our sort method to use these variables:

    sort(e) {
        if(this.sortedColumn === e.currentTarget.dataset.id){
            this.sortedDirection = this.sortedDirection === 'asc' ? 'desc' : 'asc';
        }else{
            this.sortedDirection = 'asc';
        }        
        var reverse = this.sortedDirection === 'asc' ? 1 : -1;
        let table = JSON.parse(JSON.stringify(this.People));
        table.sort((a,b) => {return a[e.currentTarget.dataset.id] > b[e.currentTarget.dataset.id] ? 1 * reverse : -1 * reverse});
        this.sortedColumn = e.currentTarget.dataset.id;        
        this.People = table;
    }  

Now our table will sort in both directions! The logic here is as follows:

  1. We check if the column clicked matches the variable sortedColumn, which holds the last column clicked. If it does, we then check the sortedDirection variable, and flip it from asc to desc, or vice versa, depending on its value.
  2. If the column clicked does not match the last column clicked, we set sortedDirection to ascending.
  3. We use the value of sortedDirection to set a value called reverse to 1 (ascending) or -1 (descending).
  4. We then go through our normal sort function, but we apply the reverse value to it. It will flip the order of the sort we have defined if reverse is equal to -1.
  5. We set the People array and we store the column that we clicked into sortedColumn.

Finally, let’s add an indicator of sort order. This gets a bit tricky. There are a few ways to do this, but let’s add an arrow to the column conditionally and delete an arrow from a column if it already exists when we click on sort. Add this code to the end of the sort method:

if(e.currentTarget.dataset.id){

    let existingIcon = this.template.querySelectorAll('img[id="sorticon"]');
    if(existingIcon[0]){existingIcon[0].parentNode.removeChild(existingIcon[0]);}

    let nodes = this.template.querySelectorAll('a[data-id="' + e.currentTarget.dataset.id +'"]')
    if(this.sortedDirection === 'asc'){icon.setAttribute('src', Images + "/images/icons/arrowup.png");}
    if(this.sortedDirection === 'desc'){icon.setAttribute('src',  Images + "/images/icons/arrowdown.png");}
    icon.setAttribute('id', 'sorticon');
    if(nodes[0]){nodes[0].children[0].appendChild(icon);}
}

A few things to note here:

  1. You need to upload a static resource file and you need it to import it into your Javascript file as so: import Images from “@salesforce/resourceUrl/Images”; You can find out how to upload a static resource here.
  2. You need to call your image whatever you reference and follow that file path. In my case, I have an images –> icons –> arrowup/down.png file in my static resource. Make your file name and path match what you reference.

Once you have the above, the code itself is pretty simple. First, we use a querySelectorAll to query the template and find an img tag with the id sorticon. We then delete it. Then, we find the node we clicked on through another querySelectorAll and we query for an a tag that has the data-id we pass in. We then build an img tag and set the icon to be up or down based on what the value of sortedDirection is. We also give it an id of sorticon so it can be deleted if another column is clicked on or the direction changes. Finally, we insert it to the tag we clicked on!

With the above, you have now implemented LWC table sort. Having a table that sorts is great start. You can now add other features, such as search.

11 thoughts on “LWC Table Sort – With Example!

  1. Thank you for this post. I am learning Javascript while also learning Lightning Web Components and there are not many posts I have found that explain each step in detail as you have. This post enabled me to implement sorting on my custom table!

    I have one follow-up question: in my case, I am using an inner SOQL statement to add certain Contact fields to my array of Account objects. I have implemented the above method and sorting works fine for all top-level Account fields, but the column headers referencing the inner Contact record fields don’t yet work as expected. How would you modify this approach to work for sub-objects?

    1. Great question! If you are adding an inner SOQL query, you are going to return a list of Contacts for each Account. If these Contacts are going into a table you are making for Accounts, you can then decide how you want to handle them. You can add them to the table as comma-separated values, or as new lines, and adjust the sorting that way.

      The biggest question here is what you expect the inner SOQL to return. If you post the query, I can add some more detail.

      1. Thank you. My goal is to show a single Contact’s name and their title in separate columns next to the Account’s name. This is displaying perfectly, but the sorting function is not sorting by the column’s data on the Contact fields as I would expect.

        My SOQL query is:
        SELECT Id, Name, (SELECT Id, Name, Titlt FROM Contacts ORDER BY Activity_Count__c DESC LIMIT 1)
        FROM Account
        WHERE ULD__c != ‘Yes’
        WITH SECURITY_ENFORCED

        My data is wired to property called ‘accounts’ so I access the values in my table using the for:each method with account.Name and then an inner-template that iterates on account.Contacts and contact.Name references the Contact. So I have two columns with ‘Name’ – one represents the Account and one represents the Contact. I can rename the Contact’s name column if needed.

      2. In your case, using the above query, the contact you are returning is an inner list of the account object. To get it to sort the same way, give it a unique data-id value and in the sort function, reference it as a special case. Have the sort function look like:

        table = table.sort((a,b) => {return a.Contact.[e.currentTarget.dataset.id] > b.Contact.[e.currentTarget.dataset.id] ? 1 * reverse : -1 * reverse});

        The data being returned for contact is really an object in Javascript. Give that a shot!

  2. Thank you for your help. Unfortunately, I have been unable to solve this. When you say “give it a unique id value” I assume you mean the column’s fieldname or value. By “reference it as a special case,” do you mean to add another if clause to the beginning of the function?

    When I replace the sort function below with the above suggestion, VS Code detects an unexpected token error

    The current code that sorts all Account columns correctly but not the Contact columns.

    sort(e) {
    if(this.sortedColumn === e.currentTarget.dataset.id){
    this.sortedDirection = this.sortedDirection === ‘asc’ ? ‘desc’ : ‘asc’;
    } else{
    this.sortedDirection = ‘asc’;
    }
    var reverse = this.sortedDirection === ‘asc’ ? 1 : -1;
    let table = JSON.parse(JSON.stringify(this.data));
    table.sort((a,b) => {return a[e.currentTarget.dataset.id] > b[e.currentTarget.dataset.id] ? 1 * reverse : -1 * reverse});

    this.sortedColumn = e.currentTarget.dataset.id;
    this.data = table;
    }

    Once I get this figured out, I’m then needing to solve how to apply it to the entire dataset when I paginate the data. Example: Even if only 10 records are showing, how to sort the whole dataset so that the top 10 are on page 1. I employed the solution at http://santanuboral.blogspot.com/2019/09/pagination-using-LWC.html for the pagination. Sorting works with it, but only by sorting the visible records on a page. As soon as you advance to the next or previous page the data needs to be resorted.

  3. Great post !!

    One issue where need your guidance please after following the above code:
    I have added the below:

    if(e.currentTarget.dataset.id){
    let existingIcon = this.template.querySelectorAll(‘img[id=”sorticon”]’);
    if(existingIcon[0]){existingIcon[0].parentNode.removeChild(existingIcon[0]);}
    let nodes = this.template.querySelectorAll(‘a[data-id=”‘ + e.currentTarget.dataset.id +'”]’)
    if(this.sortedDirection === ‘asc’){icon.setAttribute(‘src’, Images + “/images/icons/arrowup.png”);}
    if(this.sortedDirection === ‘desc’){icon.setAttribute(‘src’, Images + “/images/icons/arrowdown.png”);}
    icon.setAttribute(‘id’, ‘sorticon’);
    if(nodes[0]){nodes[0].children[0].appendChild(icon);}
    }

    where my tableheader is this:


    {clmn.Field_Display_Name__c}

    the sorting works great but the icon doesn’t show up. Where exactly did you put the sorticon id ?

    Could you please put your whole code ?

    Thanks again.

    1. Good catch. In this block of code:

      let nodes = this.template.querySelectorAll(‘a[data-id=”‘ + e.currentTarget.dataset.id +'”]’)
      if(this.sortedDirection === ‘asc’){icon.setAttribute(‘src’, Images + “/images/icons/arrowup.png”);}
      if(this.sortedDirection === ‘desc’){icon.setAttribute(‘src’, Images + “/images/icons/arrowdown.png”);}
      icon.setAttribute(‘id’, ‘sorticon’);
      if(nodes[0]){nodes[0].children[0].appendChild(icon);}

      it needs this line at the top:

      var icon = document.createElement(“img”);

      This creates the image html node needed to render the icon. Add that line at the top of this block and give it a shot.

  4. Hey There, great post. Im currently experiencing an issue where when I click the column header to sort, it actually sorts the entire table instead of the information in the column. Any idea what I could be doing wrong? Im also using template logic in the column headers, which are stored in an object in my js, I think that may be the issue, but if it is what could be the solution? thanks again for this excellent post, it is very illuminating!!!

Leave a Reply

%d bloggers like this: