Implement and Use Lightning Service Components

This was originally published on link to post

As the Lightning ecosystem evolves, I have noticed and adopted a valuable architecture pattern: Lightning service components. In this post, I will present this concept, illustrate how it is increasingly used in base components and provide tips and best practices on how developers can create their own service components.

Understanding Lightning service components

Before diving into the specifics of Lightning service components, let’s define what a service component is: A service component is a component that provides an API for a set of functionalities. Ideally, the service should be specialized, generic and reusable.

Another important thing that differentiates a service component from other components is the fact that it does not have a graphical representation. Unlike other components, it is not visible by default. However, it can display some graphics (like a modal dialog or a toast notification) upon request if it’s a UI service.

We will use the term “caller” throughout this post to designate a component calling a service.

An overview of base service components

Since the introduction of the Lightning Data Service (the first base service component) in Winter ’17, the number of base service components has steadily increased over releases. As of Summer ’18 there are 13 base service components and this number is likely to grow in the upcoming releases.

Summer ’18 base service components

Here is an overview of the Summer ’18 base service components. Notice that the terms “library,” “API” and “service” are used interchangeably but these are all service components.

Name Component Category Description
Lightning Data Service force:recordData Data Provides the ability to create, read, update, and delete Salesforce records in Lightning without the use of an Apex controller.
Notification Library lightning:notificationsLibrary UI Displays messages via notices and toasts.
Overlay Library lightning:overlayLibrary UI Displays messages via modals and popovers.
Workspace API lightning:workspaceAPI UI API for accessing/manipulating workspaces (Tabs and Subtabs).
Utility Bar API lightning:utilityBarAPI UI API for the Utility Bar.
Navigation Service lightning:navigation UI Allows to navigate to a given page or to generate a page URL.
Navigation Item API lightning:navigationItemAPI UI Allows to control navigation items in Lightning console apps, where navigation items display in an item menu.
Quick Action API lightning:quickActionAPI UI Allows to control actions in Lightning Experience on record pages.
Conversation ToolkitAPI lightning:conversationToolkitAPI Service Cloud Console integration API for Live Agent.
Omni-Channel Toolkit API lightning:omniToolkitAPI Service Cloud Provides access to the API for the Omni-channel toolkit.
Minimized API lightningsnapin:minimizedAPI Service Cloud Enables customization of the user interface for the minimized snap-in in Snap-ins for web.
Pre-chat API lightningsnapin:prechatAPI Service Cloud Enables customization of the user interface for the pre-chat page in Snap-ins Chat.
Settings API lightningsnapin:settingsAPI Service Cloud Enables to fetch certain settings from within custom components for Snap-ins for web.

Using a base service component

All of the base components are documented with examples. For the sake of brevity we won’t examine all of them in detail in this post but let’s have a look at an example: displaying a notification with the notifications library.

The first step in using the notifications library or any other service is to add the service component to a caller component’s markup:

<aura:component>
    <!-- Service component -->
    <lightning:notificationsLibrary aura:id="notifLib"/>
    <!-- Sample button that calls the service -->
    <lightning:button name="notice" label="Show Notice" onclick="{!c.handleShowNotice}"/>
</aura:component>

Notice that we included an aura:id attribute in our lightning:notificationsLibrary tag. This attribute allows us to declare a local ID that identifies the component as notifLib. This helps us to get programmatic access to the service component later.

The second step is to call our service with some JS code in our caller component’s controller. In our example we have a button that triggers a handleShowNotice controller function. This function displays the notification via the service.

handleShowNotice : function(component, event, helper) {
    component.find('notifLib').showNotice({
        variant: 'error',
        header: 'Something has gone wrong!',
        message: 'Unfortunately, there was a problem.'
    });
}

The first thing that our function does is to retrieve a reference to our service component by using the find method with our local id (notifLib). Once we have the reference, we can then call an Aura method that provides the service. In our case, we call the showNotice method with some parameters (an object with properties that describe the notification). This displays a notification.

This pattern of declaring a service component in markup and executing its Aura methods is the key to using service components.

Implementing and using a custom service component

In addition to the growing list of base service components, you may want to develop custom services. Here are a few simple steps on how to proceed:

  1. Identify a custom service candidate
  2. Implement the service
  3. Use the service

Identify a custom service candidate

When looking for a custom service candidate, you want to ask yourself a couple of questions such as:

  • Is the JS code of your existing component’s “polluted” by some non-core functions?
  • Do you find yourself writing the same bit of code over and over again?
  • Can some of your code be reused?

Creating a custom service will simplify your component’s code and help you reduce code duplication. This in turns makes your code more robust and facilitates maintenance.

Let’s take a concrete example: calling a server-side action. This is a great match for a service as we all have copied the documentation’s JS template over and over again. However, the fact is that out of the hundred of lines of code that this represents (if you handle errors thoroughly), there is only one block of instructions that really varies: processing the server response.

This is why I contributed a Server Action Service, an ideal candidate for a service component. This service reduces the hundreds of lines of code required to call a server action and handle errors to about ten lines for complex cases (we will provide this code later in this article):

const server = cmp.find('server');
const actionReference = cmp.get('c.anAction');
server.callServer(
    actionReference,
    actionParameters,
    isCacheEnabled,
    $A.getCallback(response => { /* Optional success callback */ }),
    $A.getCallback(errors => { /* Optional error callback */ }),
    isErrorNotificationEnabled
);

For simple use cases, server calls can even be inlined:

cmp.find('server').callServer(cmp.get('c.anAction'), params);

Using this service lets you focus on what matters the most and also takes care of handling errors in a unified way (displaying an error toast notifications and detailed error logs in the developer console).

Implement the custom service

When implementing a custom service, there are a couple of architectural approaches that you can explore. Here are some tips on how to adopt the best implementation strategies.

Injecting your service
A simple approach based on object-oriented programming is to use inheritance to inject your service. In this approach, the caller component inherits from the service:

<aura:component extends="c:MyService">
    ...
</aura:component>

However, inheritance is not the best service injection strategy for a couple of reasons:

  • A component can only inherit from a single parent so this hampers your ability to use multiple services in the same component.
  • Lightning component inheritance has some limitations.

The best alternative to inheritance is to use composition. That is, declaring an instance of a service in the component that will use it. This lets you declare and use several services in the same caller component:

<aura:component>
    <c:ServerActionService aura:id="serverService"/>
    <c:PicklistService aura:id="picklistService"/>
    ...
</aura:component>

Exposing your service
Once you have settled on how to inject the service, you need to decide how to expose its functionalities. There are two options: using events or a combination of Aura methods and callback functions.

Using events may seem the easiest way to call a service because of the low coupling that it implies:

  • if using an application event, a service instance can be called from anywhere in the Lightning app.
  • if using a component event, a service instance can be called by any component in the ancestry branch (parent or child) of its container.

However, there are some important downsides to the use of events:

  • Your service has to be a singleton (there can be only one instance of it) within the “reach” of the event. If not, the event will be handled by multiple service instances and every service call will be duplicated.
  • Events do not offer an easy way to provide callbacks (it lets you trigger the service but you cannot get a response from it).

A more flexible approach is to use the combination of an Aura method with one or several callback functions. Interestingly, Aura methods are the only place where you can define an Aura attribute of type Function. This is extremely convenient as it allows to implement methods that return results asynchronously.

For example, in the server action service, we have a callServer Aura method which specifies two callback functions successCallback and errorCallback:

<aura:method access="global" name="callServer" action="{!c.callServer}" description="...">
    <aura:attribute name="action" type="Map" required="true" description="..."/>
    <aura:attribute name="params" type="Map" description="..."/>
    <aura:attribute name="isStorable" type="Boolean" default="false" description="..."/>
    <aura:attribute name="successCallback" type="Function" description="..."/>
    <aura:attribute name="errorCallback" type="Function" description="..."/>
    <aura:attribute name="disableErrorNotification" type="Boolean" description="..."/>
</aura:method>

The component that calls the service executes the callServer method by specifying a success and an error callback function (the code of these functions lives in the calling component). Then the service executes itself and calls the appropriate callback function with some results or errors.

Making your service generic by decoupling it from a specific Apex controller
Some services like the server action service require access to an Apex controller. This implies a certain level of coupling between the service component and it’s server controller. Unfortunately, such coupling goes against the principle of having generic services so we must break it. There are a two of ways to achieve this: moving the server controller out of the service component or implementing a generic Apex controller.

In the context of the server action service, there is no way to implement a truly generic Apex controller so we have to avoid declaring the controller in the service. In order to do that, we let the calling component specify an Apex controller. We then obtain a reference to a server action and pass it to the server action service.

Here are the four steps that illustrate how the server action service works (see diagram above):

  1. Calling component controller gets a reference to the server-side action (the Apex @AuraEnabled method)
  2. Calling component controller executes the service’s callServer Aura method with:
    1. the server-side action reference
    2. a reference to a success callback function
    3. optionally a reference to an error callback function
  3. Service executes the server-side action and gets the server response
  4. Service either
    1. calls the success callback function with the server response
    2. calls the error callback function with the server error and handles error notification

An alternative to this approach is to implement a generic Apex controller. For example, I have contributed a picklist service that retrieves the entries of any standard or custom picklist field. In such use cases, you do not want to create as many server actions as there are picklist types because this is a repetitive manual process. Plus, it is unmaintainable and does not scale well.

The solution to that problem is to implement a single server-side action but to make it fully generic by leveraging dynamic Apex and dynamic class instantiation. The complexity of the Apex code is beyond the scope of this post but here is the signature of the generic server-side action that returns picklist entries:

@AuraEnabled
public static List<Map<String,Object>> getEntries(String objectName, String fieldName) {
    // Dynamically load object and field based on their names
    // then, load and return picklist entries
}

This allows the caller component to call the service with any picklist field:

const service = cmp.find('picklistService');
// Getting picklist entries for Account.Industry field
service.getEntries('Account', 'Industry', entries => {
    console.log(entries);
});
// Getting picklist entries for a custom object and field
service.getEntries('TrailheadUser__c', 'Rank__c', entries => {
    console.log(entries);
});

While this dynamic Apex approach is extremely useful, bear in mind that it has the drawback of being slightly unsafe as it defeats compiler checks. You could be calling a service on a class or object that does not exist but the compiler will not throw an error when you save your code. You will only get an error at run time so this is harder to test and debug.

Use the custom service

Using a custom service is similar to using a base service component. To recap, these are the three steps:

  1. Add the service component with an aura:id in a caller component’s markup.
  2. Get a reference to the service by calling component.find() with the previously defined aura:id in the caller’s JS controller or helper.
  3. Execute one of the service’s Aura methods.

Closing words

As you have seen by now, Lightning service components are becoming more prevalent in the Salesforce Platform. This architecture pattern is an efficient and safe way to reuse code across different projects. Give it a try by using one of the base service components and move to the next step by creating your own!

Over the course of my different projects I have assembled a list of services that I often reuse. Feel free to check out my contributions:

Learn more about how to write object-agnostic Apex for your Lightning components with the Build Reusable Lightning Components Trailhead project.

About the author

Philippe Ozil is a Senior Developer Evangelist at Salesforce where he focuses on the Salesforce Platform. He writes technical content and speaks frequently at conferences. He is a full stack developer and a VR expert. Prior to joining Salesforce, he worked as a developer and consultant in the Business Process Management (BPM) space. Follow him on Twitter @PhilippeOzil or check his GitHub projects @pozil.

Continue Reading this post here  Salesforce

Philippe Ozil

Leave a Reply

Your email address will not be published. Required fields are marked *