Saturday 19 February 2022

Lightning Web Component Getters

Introduction

When Lightning Web Components were released, one feature gap to Aura components I was pleased to see was the lack of support for expressions in the HTML template. 

Aura followed the trail blazed by Visualforce in allowing this, but if not used cautiously the expressions end up polluting the HTML making it difficult to understand. Especially for those that only write HTML, or even worse are learning it. Here's a somewhat redacted version from one of my personal projects from a few years ago:

Even leaving aside the use of i and j for iterator variables, it isn't enormously clear what name and lastrow will evaluate to.

Handling Expressions in LWC

One way to handle expressions is to enhance the properties that are being used in the HTML. In the example above, I'd process the ccs elements returned from the server and wrap them in an object that provides the name and lastrow properties, then change the HTML to iterate the wrappers and bind directly to those properties. All the logic sits where it belongs, server side. 

This technique also works for non-collection properties, but I tend to avoid that where possible. As components get more complex you end up with a bunch of properties whose sole purpose is to surface some information in the HTML and a fair bit of code to manage them, blurring the actual state of the component. 

The Power of the Getter

For single property values, getters are a better solution in many cases. With a getter you don't store a value, but calculate it on demand when a method is invoked, much like properties in Apex. The template can bind to a getter in the same way it can to a property, so there's no additional connecting up required.

The real magic with getters in LWC is they react to changes in the properties that they use to calculate their value. Rather than your code having to detect a change to a genuine state property and update avalue that is bound from the HTML, when a property that is used inside a getter changes, the getter is automatically re-run and the new value calculated and used in the template.

Here's a simple example of this - I have three inputs for title, firstname and lastname, and I calculate the fullname based on those values. My JavaScript maintains the three state properties and provides a getter that creates the fullname by concatenating the values:

export default class GetterExample extends LightningElement {
    title='';
    firstname='';
    lastname='';

    titleChanged(event) {
        this.title=event.detail.value;
    }

    firstnameChanged(event) {
        this.firstname=event.detail.value;
    }

    lastnameChanged(event) {
        this.lastname=event.detail.value;
    }
    
    get fullname() {
        return this.title + ' ' + this.firstname + ' ' + this.lastname;
    }
}

and this is used in my HTML as follows:

<template>
    <lightning-card title="Getter - Concatenate Values">
        <div class="slds-var-p-around_small">
            <div>
                <lightning-input label="Title" type="text" value={title} onchange={titleChanged}></lightning-input>
            </div>
            <div>
                <lightning-input label="First Name" type="text" value={firstname} onchange={firstnameChanged}></lightning-input>
            </div>
            <div>
                <lightning-input label="Last Name" type="text" value={lastname} onchange={lastnameChanged}></lightning-input>
            </div>
            <div class="slds-var-p-top_small">
                Full name : {fullname}
            </div>
        </div>
    </lightning-card>
</template>

Note that I don't have to do anything to cause the fullname to rerender when the user supplies a title, firstname or lastname. The platform detects that those properties are used in my getter and automatically calls it when they change. This saves me loads of code compared to aura.

You can also have getters that rely on each other and the whole chain gets re-evaluated when a referenced property changes. Extending my example above to use the fullname in a sentence:

get sentence() {
    return this.fullname + ' built a lightning component';
}

and binding directly to the setter:

<div class="slds-var-p-top_small">
    Use it in a sentence : {sentence}
</div>

and as I complete the full name, the sentence is automatically calculated and rendered, even though I'm only referencing another getter that was re-evaluated:




You can find the component in my lwc-blogs repository at : https://github.com/keirbowden/lwc-blogs/tree/main/force-app/main/default/lwc/getterExample

Another area that Lightning Web Components score in is they are built on top of web standards, so if I want to change values that impact getters outside of the user interactions, I can use a regular setinterval  rather than having to wrap it inside a $A.getCallback function call, as my next sample shows:


In this case there is a countdown property that is calculated based on the timer having been started and not expiring, and an interval timer that counts down to zero and then cancels itself:

timer=30;
interval=null;

startCountdown() {
    this.interval=setInterval(() => {
        this.timer--;
        if (this.timer==0) {
           clearInterval(this.interval);
        }
    }, 1000);
}

get countdown() {
    let result='Timer expired';
    if (null==this.interval) {
        result='Timer not started';
    }
    else if (this.timer>0) {
        result=this.timer + ' seconds to go!';
    }

    return result;
}
and once again, I can just bind directly to the getter in the certainty that if the interval is populated or the timer changes, the UI will change with no further involvement from me.
<div class="slds-var-p-top_medium">
    <div class={countdownClass}>{countdown}</div>
</div>

Note that I'm also using a getter to determine the colour that the countdown information should be displayed in, removing more logic that would probably be in the view if using Aura:



You can also find this sample in the lwc-blogs repo at : https://github.com/keirbowden/lwc-blogs/tree/main/force-app/main/default/lwc/getterCountdown

Related Posts 

2 comments:

  1. A great read as usual thanks Keir!

    ReplyDelete
  2. My experience is that it doesn't detect the change, but rather it requires a rendered property elsewhere on the component to change and force a re-render. That is, the fact you have the parts referenced in "value" properties means that a change to a part causes a re-render and *that* causes the getter to be re-evaluated. Is your experience different? Has this behaviour changed recently?

    ReplyDelete