Lightning Web Components: Service Components

As you dive deeper into the world of Lightning Web Components (LWC), it’s inevitable that complexity will increase. With increased complexity, you will need to write modular reusable code. In this blog post, I’ll explore how you can create reusable LWC code using service components.

Service components are not new to developers using the Lightning Component Framework. My colleague Philippe Ozil wrote a comprehensive post on how to tackle service components using the Aura programming model last year. There is also a learning unit in the Build a Lightning Component to Override a Standard Action module on Trailhead which shows how to implement an Aura service component. If you’ve adopted and used those patterns, you might be wondering to yourself, “How can I apply the principles of service components (and more fundamentally code reuse) when developing Lightning Web Components?”

The good news is that understanding code reuse in LWC is primarily about understanding the standard JavaScript module feature proposed in ES2015. This standard is now widely used and adopted by browser manufacturers.

Carrying the ES module standard

JavaScript was not born with a modular programming architecture. As it matured, different code patterns and libraries emerged in the ecosystem. Eventually modules were codified into the language.

There’s no “module” keyword in JavaScript, but syntactically, a module is where a JS file makes available one or more bits of functionality using the export keyword. When designing a module, the developer should keep in mind that a module should be one or more small units of code that are meant to be reused. Each unit of code should be intended to solve one discrete problem.

The ES2015 standard allows developers to make use of modules either by using the HTML script tag to import the functionality using <script type="module"> or by using the import keyword in another JavaScript module. The latter is precisely how Lightning Web Components developers use modules.

This is what it looks like:

// maths.js
function adder(x, y) {
    return x + y; 
}

function subtractor(x, y) {
    return x - y; 
}

export {adder, subtractor};

Let’s say we wanted to use the adder function in our app so that it added the numbers one and two. We would import and invoke it like this:

// app.js
import {adder, subtractor} from './maths';

let sum = adder(1, 2);

While this example shows a module with a couple of exported functions, perhaps you have a set of features you want to group into a JavaScript class. Taking the previous example, what if you have more than just two mathematical operations? You could implement a module that looks like this:

export default class MathsClass {

    add(x, y) {
        return x + y; 
    }

    subtract(x, y) {
        return x - y; 
    }

    multiply(x, y){
        return x * y; 
    }

    divide(x, y) {
        return x / y; 
    }
    
    findRemainder (x, y) {
        return x % y; 
    }

}

And in this instance, using the multiply method to multiply 5 and 10, it would look like this:

import MathsClass from './mathsClass';

const maths = new MathsClass();
let product = maths.multiply(5, 10);

This isn’t meant to be a modules master class, but hopefully those new to the ES2015 modules features now have an idea of how to both create and use a module — which is right where we want to be when dealing with code reuse in Lightning Web Components.

Modules everywhere

If you’ve even done a little bit of building LWCs so far, you’ll have seen that import and export are everywhere. Let’s look at a little example from the Recipes sample app, the apexWireMethodToProperty component.

import { LightningElement, wire } from 'lwc';
import getContactList from '@salesforce/apex/ContactController.getContactList';

export default class ApexWireMethodToProperty extends LightningElement {
    @wire(getContactList) contacts;
}

First of all, the file that you write that contains JavaScript in your component is a module. You can see that quite plainly by the export statement. As we saw earlier in the simple module use cases, the class is being made available for other modules to use. The framework expects each Lightning web component to make available a default class export that extends LightningElement.

The LWC module also makes use of other modules. The first two lines are importing functionality we want to use in our component. The first line imports some features from the core LWC library that represent the LightningElement parent class, and the @wire decorator. The second line is importing the API that surfaces the Apex method getContactList. All of these things follow the standard module architecture.

So what happens when you want to create some reusable code for another component? Back to that in a moment.

A use case for reusability

Once I’d gotten the basics of LWC under my belt, I went looking for an interesting use case to build. I settled on a UK postcode lookup. There were a few reasons for this: First, I live in the UK. Second, being an immigrant here, I’ve become an admirer of the UK postcode system (don’t judge me). Third, most software builds with UK users in mind uses a postcode/address lookup feature, which given the global universal nature of Salesforce, does not exist. Finally, it involves a callout to a web service, giving me a chance to have my component interact with a third-party API, which was something I personally wanted to incorporate into what I built.

In the UK the typical flow for getting a person’s address usually begins with a postcode, then a house number or name. With those two pieces of information, you know the rest of the address. This has to do with the relatively small number of addresses within a given postcode (about 50 on average). There are a number of web services that provide this address lookup functionality, so with my idea in mind, I picked one and set about getting my idea to work in an LWC.

As you can imagine, this being my first go at building my own LWC from scratch, the roughing out of my component resulted in somewhat of a monolith, containing everything as I built out bits of what I wanted it to do. The one piece I knew I would breakout into its own artifact was the code concerning the callout to the address lookup service. So let’s look at what that looks like in LWC.

More modules

A Lightning web component is, at a very minimum, comprised of a single JS module (and the metadata XML file, of course). Other files are optional depending on what functionality you want to build into your component. If it is meant to render a UI, there will be an HTML template. If custom styling is needed, there will be a CSS file. And if for the purposes of modularity it makes sense to encapsulate some logic in its own file, you can have additional JS modules. There is an example of this in the libsD3 component bundle in the LWC Recipes sample app.

But often it makes sense to have such code available for use across many different components. Say my postcode lookup example. Could the access of the postcode API be something that might need to be used in other components? Absolutely.

This is where the service component comes in. A service component is an LWC that’s only job is to provide logic or functionality that’s meant to be accessible by several other components.

For the postcode lookup, the main API I was concerned with for my component was to input a postcode and receive back the list of addresses for that postcode. The first iteration of my service component surfaced a single JS function that did take the postcode plus an API key and did just that. This was simple and clean and looked like this.

const _getResponseError = (response) => {
    //...translate API short status to human readable errors
}


const findAddressesFromPostcode = (apiKey, postcode) => {

    const calloutURI = `https://api.getAddress.io/find/${postcode}?api-key=${apiKey.data}&expand=true`;

    // the fetch API is not supported or polyfilled in IE11.
    // in this case use XMLHttpRequest instead. 
    // requires whitelisting of calloutURI in CSP Trusted Sites
    return fetch(calloutURI, {
        "method": "GET"
    }).then(
        (response) => {
            if (response.ok) {
    
                return response.json();
    
            }
    
            throw _getResponseError(response);
    
        }
    );
};

export { findAddressesFromPostcode };

Eventually I began to consider other features that were surfaced by this API. These included another endpoint to look up both a postcode and building number/name to fetch a single address. There was another that returned the distance between two postcodes. There were also a number of administration endpoints for managing the web service account.

At this point, looking at upwards of ten different endpoints, plus the prospect of having to pass in a key to each and every surfaced function, I decided to move to a module that exported a class. This meant I could pass the connection data into the object once upon constructing the API connection object, and then use whichever methods needed. This would make a more flexible surfacing of the API, should I move forward and use more of the features in the future.

const _urlRoot = 'https://api.getAddress.io';

export default class GetAddressConnector {

    constructor(apiKey){
        this._apiKey = apiKey;
    }

    // no param required for session ID as we've stored it in context
    findAddressesFromPostcode = (postcode) => {

        // urlRoot and apiKey props used 
        const calloutURI = `${_urlRoot}/find/${postcode}?api-key=${this._apiKey}&expand=true`;
    
        // the rest of this is the same
        return fetch(calloutURI, {
            'method': 'GET'
        }).then(
            (response) => {
                if (response.ok) {
    
                    return response.json();
    
                } 
    
                throw this._getResponseError(response);
    
            }
        );
    }; 

    // other endpoints also implemented as methods
    findAddressesFromPostcodeAndHouse = (postcode, houseNumber) => {...};

    getDistanceBetweenPostcodes = (postcodeFrom, postcodeTo) => {...};

    getLimits = () => {...};

    getLimitsOnDay = (day, month, year) => {...};

}

In the implementation above, the class stores the API key via the constructor. Each method can then make use of this without having to pass it each request. The endpoint URL root is set as a private constant (outside of the class scope) also making it available for the functions in the class but inaccessible from outside the module.

Service components for code reuse

While this example shows the use case of a service component that surfaces a REST API, there are many other cases where this could be useful. Customers may develop a set of common utilities that are used across many projects. Developers or system integrators may also do the same, to bring a common set of functionality they use in all their customers. And of course, ISVs may wish to create a set of features that developers can use to interact with functionality in their package or in an API that works with a service that lives in another cloud provider.

However you use them, service components will make your code more reusable, and more modular. And while I’ve not covered testing in this blog post, smaller more modular units of code will also improve testability. Why not look now how you can make use of service components as you build out your Lightning Web Component footprint?

Resources

Address Lookup LWC Component Code on Github
LWC Developer Guide: Share Code

Leave your comments...

Lightning Web Components: Service Components