7

What's the best practice for sharing a service method between lightning components? Let's say I have a function getUrlParam(paramName) that gets the value from a url parameter. I have many lightning components that need to be able to use this function. Here are the following approaches I am aware of with the cons of each:

  1. Copy the same code in each component's helper. This is a terrible coding pattern since now we have distributed the same code all over and have to apply a bugfix to many locations.
  2. Create an extensible component and have all components extend it. This becomes messy since components can only extend a single other component so we will end up chaining extensions which makes maintaining these components much more difficult.
  3. Create a utility component with public methods. This appears to simply not work since aura methods always return undefined.

Does anyone have any better options for setting up service methods to be shared among lightning components?

  • Number 3 is actually a viable option. You can use a callback to get results back to the caller. In winter 18, you will finally be able to get results back from synchronous method calls!! – Caspar Harmer Aug 23 '17 at 23:20
  • Here is the quote: "We made it easier to return a result from aura:method, which is used to communicate from a parent component to a child component that it contains. Use the return statement to return a value from synchronous JavaScript code." ref: releasenotes.docs.salesforce.com/en-us/winter18/release-notes/… – Caspar Harmer Aug 23 '17 at 23:32
10

Realistically, you'll probably want to create a Static Resource, and use this as a shared resource, also outlined in Modularizing Code in Lightning Components. In your JavaScript file, you'll want to define all your methods.

window._myMethod = function(...) { ... }

Then, in your Lightning component markup:

<ltng:require scripts="{!$Resource.myJsUtils}"
              afterScriptsLoaded="{! ... }"/>

Note that "window" in this code actually ends up being the SecureWindow, not the global "window" provided by the browser, and this is automatically grafted into your code, so you can write:

({ // This is my controller
    someMethod: function(component, event, helper) {
        _myMethod( ... );
    }
})

The service component method (the last one mentioned in your question) is also possible. What you need to do though, is to provide a callback (mentioned in Modularizing Code in Lightning Components). The example for this looks like the following:


Service Component

<aura:component controller="AccountController">

    <aura:method name="findAll" action="{!c.findAll}">
        <aura:attribute name="callback" type="function"/>
    </aura:method>

</aura:component>

Service Component Controller

({
    findAll : function(component, event, helper) {
        var params = event.getParam("arguments");
        var action = component.get("c.getAccounts");
        action.setCallback(this, function(response) {
            if (response.getState() === "SUCCESS") {
                params.callback(null, response.getReturnValue());
            } else {
                params.callback(response.getError());
            }
        });
        $A.enqueueAction(action);
    }
})

Which you reference in your other components:

<aura:component>

    <aura:attribute name="accounts" type="Account[]"/>

    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>

    <c:AccountService aura:id="service"/>

    <ul>
    <aura:iteration items="{!v.accounts}" var="account">
        <li>{!account.Name}</li>
    </aura:iteration>
    </ul>        

</aura:component>

And is then called in the following manner:

({
    doInit : function(component, event, helper) {
        var service = component.find("service");
        service.findAll($A.getCallback(function(error, data) {
            // TODO: Add error handling
            component.set("v.accounts", data);
        }));
    }
})
  • Thanks for the link and detailed info. I was hoping the framework might have a better answer for this scenario but these are certainly a step in the right direction for me. It's a bit of a pain that these introduce asynchronous patterns for what would otherwise be a synchronous function: in the resource case we can't invoke the method until the script is loaded (even if the component's init event is firing) and for the service component callback we need an anonymous function to continue processing with the value. I'll see what setting these up looks like in practice for our use case. – dsharrison Aug 23 '17 at 20:51
  • @dsharrison Yeah, I think the static resource bit is a little less annoying (because you're really just moving the init logic to the afterScriptsLoaded handler), but use case will matter. It's still (marginally) better than trying to figure out a component hierarchy. – sfdcfox Aug 23 '17 at 21:01
6

In Winter 18, you can create a utility class and use it to directly return results without using a callback.

Quote:

We made it easier to return a result from aura:method, which is used to communicate from a parent component to a child component that it contains. Use the return statement to return a value from synchronous JavaScript code.

Ref here.

So you'd define your utility component:

<aura:component >
  <aura:method name="addNums" action="{!c.addNumbers}">
    <aura:attribute name="numbers" type="Integer[]"/>
  </aura:method>
</aura:component>

Controller or Helper:

({
  addNumbers : function(component, event, helper) {
      var params = event.getParam("arguments");
      var total = 0;
      params.numbers.forEach(function(num){
        total += num;
      });
      return total;
  }
})

And you'd call this method like so:

<aura:component>
  <c:mathUtils aura:id="mathUtils"/>
  <button onclick="{!c.getTotal}">Get Total!!</button>
</aura:component>

Controller/Helper:

getTotal : function(component, event, helper) {
  var numbers = [1,2,3];
  var total = component.find("mathUtils").addNums(numbers);
  alert(total);
}

NOTE THIS IS WINTER 18... So not available for use quite yet :)

  • This will certainly be our preferred approach once this is available. – dsharrison Aug 24 '17 at 0:03
  • @Casper - Maybe I am reading wrong but this does not read well: "return a result from aura:method, which is used to communicate from a parent component to a child component that it contains" - the parent is the one calling a method in the child (child to parent) but that is not how the statement reads to me – Eric Aug 24 '17 at 0:04
  • Hah well, you'll have to bring that up with SF - that's a quote. Perhaps I should have added (sic) to it... :). I agree though - extremely clumsy wording – Caspar Harmer Aug 24 '17 at 0:32
  • @CasparHarmer - Sorry for some reason I though you were one of the SF folks. lol – Eric Aug 24 '17 at 1:25
  • Hah, maybe it's my awesome answers... No, I'm just a developer from NZ – Caspar Harmer Aug 24 '17 at 1:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.