Those of us that have been developing in Lightning for a while have always tried to follow the platform’s best practices. For the most part, that means creating tons of reusable components that get added to the page either dynamically (via $A.createComponents) or by simply placing their <ui:> or <lightning:> tags in the markup. For most normal high-level components, with only a few of these subcomponents inside them (think about a related list with 2-3 rows, or a page with 5-6 cards), this approach presents little drawback and has minimal impact in their overall performance.

However, this approach does not scale well when you are building large components that can include hundreds, if not thousands, of subcomponents. Imagine this scenario: a related list that has about 10 columns, with 200 rows in it.

The Architecture for such a component could look like this:

c:Table
   → Collection of c:Row
      → Collection of c:Cell

Imagine, as well, that the data is dynamic so the developer decided to make the cell capable of handling several data types. Its component markup looks like this:

<div id=”cellContainer”>
    <aura:if isTrue=”<A Number>”>
        <ui:inputNumber….>
        <aura:set value=”else”>
            <aura:if isTrue=”<A Date>”>
                <ui:inputDate…>
                <Continue with many more data types>
            </aura:if>
        </aura:set>
    </aura:if>
</div>

In principle, this looks like a sensible best-practice-oriented approach to building a table. However, this approach generates about 2200 components (2000 cells, 200 rows), plus headers, plus all the related list decoration (title, refresh/filter buttons, etc), plus all the normal Lightning overhead. In addition to all of that, the platform includes non-rendered elements and attributes, as well as markup as components in the Component Tree. Overall, you can end up with 50, 100 or even 200 components per cell. However, the Lightning Framework begins to experience poor performance when the Component Tree includes a large number of components (35,000+), but your performance will vary based on platform, browser and computer hardware. Obviously, the more rows you add to this table, the more likely it is for you to end up with a view that is so slow that it blocks, or even crashes, the browser. In addition, because the Component Tree is also global to the application container, when you open several instances of this table you get to see the Component Tree component count grow exponentially.

Depending on your number of columns and cells, there are some progressive steps you can make to reduce the number of components in your Component Tree, thereby increasing performance of the application framework.

Solution #1: avoid <aura:if> tags for components whose data doesn’t change

The Lightning platform is smart. It wants your component to perform. However, you can’t have it both ways. Either your component will load quickly and then re-render a bit slower, or it will load a bit slower but re-render superfast. Salesforce chose to optimize for the latter. Therefore, the compiler will add all the events and overhead that it will need up front. If you render your data once and never change it (like in the case of the table we are discussing in this article) then you are paying a very high price here. You will both add a large number of components to the Component Tree and waste a lot of time waiting for these cells to render. Instead, shift the work to the component’s JS controller and let the markup be lightweight.

An example would be like this:

Markup

<div id="cellContainer">
    {!v.body}
</div>
Note: v.body is an attribute you get “for free” with every component. 
This is an array of Aura.Component elements.

Controller

var componentName = '';
var options = {value: <theValue>};
if (<A Number>) {
    componentName = 'ui:outputNumber';
} else If (<A Date>) {
    componentName = 'ui:outputDate';
}            <Continue with many more data types>
$A.createComponent(componentName, options, function(comp) {
    var body = component.get("v.body");
    body.push(comp);
    component.set("v.body",cmp);
});

With this approach you create only one component, and avoid paying the price of the platform wiring for potential re-renders that will never happen and the time wasted evaluating every single clause in those many <aura:if> clauses.

Solution #2: avoid components altogether

In severe cases, the solution #1 is not enough. Maybe you have too many rows, or have to display several of these tables and you experience the poor performance limit immediately, or while adding more components.

In these cases, the best idea is to simply do away with the component hierarchy altogether and go old school. You can use jQuery, for example, and generate all of the markup yourself.

For example:

Markup (for c:Table)

<table id=”theTable”>
</table>

Controller

for (row in rows) {
    $row = $('<tr>');
    for (col in columns) {
        $cell = $('<td>');
        var value;
        if (<A Number>) {
           value = $A.localizationService.formatNumber(<theValue>);
        } else if (<A Date>) (
            value = $A.localizationService.formatDate(<theValue>);
        } <Many More Data Types>
        $cell.html(value);
    
        $row.append($cell);
    }
    
    $('#theTable').append($row);
}

This approach eliminates your component tree growth, and it performs really well at high row-counts. There are two main drawbacks. The first is that losing the components makes the code less reusable, the second is that you have to format the data yourself, and can no longer depend on <ui:outputXXX> to do it for you. I think this is a small price to pay for performance.

Final Thoughts

In summary, while depending on markup and subcomponents makes your code clean and easy to read, you must be smart in following best practices when your data is read-only and when your component counts are likely to grow significantly if you want to keep your product performant and scalable. 


Want to take your app to the next level? Learn how CodeScience can help at www.codescience.com/services.