While it’s nice that the Lightning Framework provides a way for Aura components to invoke Apex methods, the code required is a bit verbose. We can do better.

First let’s look at how Salesforce recommends you make a server-side call. The following is how you would invoke and handle one controller method from your Aura component:

getFavoriteThings: function(component, numberOfThings) { // Create method invocation action const action = component.get('c.getFavoriteThing_apex'); // Set any parameters action.setParams({ num: numberOfThings || 1 }); // Establish result response action.setCallback(this, response => { const state = response.getState(); const value = response.getReturnValue(); const error = response.getError(); switch (state) { case 'SUCCESS': // do something with your favorite things console.log(value); break; case 'ERROR': case 'INCOMPLETE': // do something with the error console.error(error); break; case 'NEW': case 'RUNNING': default: break; } }); // Invoke the action $A.enqueueAction(action); }

Now repeat that for every different Apex method you need to invoke. Kill me now. #CodeExplosion #DontReallyKillMe

Extract. Encapsulate. Improve. Rejoice.

Luckily there’s a way to keep our sanity. Here’s a simple service component that encapsulates the required functionality, makes it argument driven, and returns a Promise .

ApexProxy.cmp <aura:component> <aura:method name='invoke' description='Invokes an @AuraEnabled method on an Apex controller.'> <aura:attribute name='caller' type='Object' description='The calling component, to which the target Apex controller has been attached.' /> <aura:attribute name='method' type='String' description='The name of the method to be invoked.' /> <aura:attribute name='params' type='Object' description='A map of arguments to pass with the method invocation. Each key should match the name of a parameter on the target Apex method.' /> </aura:method> </aura:component> ApexProxyController.js ({ invoke: function(component, event, helper) { const args = event.getParam('arguments'); return helper.invoke(args.caller, args.method, args.params); } }) ApexProxyHelper.js ({ invoke: function(caller, method, params) { // return a promise which will either be resolved or rejected return new Promise($A.getCallback((resolve, reject) => { // Create method invocation action const action = caller.get('c.' + method); // Establish action parameters action.setParams(params || {}); // Establish result response // Resolve on success, reject on error/incomplete, ignore otherwise action.setCallback(this, response => { const state = response.getState(); const value = response.getReturnValue(); const error = response.getError(); switch (state) { case 'SUCCESS': resolve(value); break; case 'ERROR': case 'INCOMPLETE': reject(error); break; case 'NEW': case 'RUNNING': default: break; } }); // Don't batch actions action.setBackground(true); // Invoke the action $A.enqueueAction(action); })); } })

Using this component is as simple as including it in your other components, calling the invoke method when needed, and handling the settled promise.

Component <component controller='myController'> <!-- include the Apex Proxy in your markup --> <c:ApexProxy aura:id='apexProxy' /> <!-- whatever else you need --> </component> Helper ({ getFavoriteThing: function(component, numberOfThings) { // Get the Apex proxy const apexProxy = component.find('apexProxy'); // Call the invoke method and handle the (eventual) response apexProxy.invoke(component, 'getFavoriteThing_apex', {num: numberOfThings}) .then($A.getCallback(favoriteThing => { // Do something with your favorite thing console.log(favoriteThing); })) .catch($A.getCallback(error => { // Do something with the error console.error(error); })); } })

Some notes…

The Apex Proxy does not need to have a controller defined for it as it automatically knows which controller to use because we pass a component instance into the invoke method.

instance into the invoke method. Batching actions is disabled due to the fact that enqueued actions all run as one transaction and are beholden to governor limits. action.setBackground(true) ensures that the Apex invocation runs in its own transaction.

There it is - simple and easy! Later we will discuss how we can simplify our codebase even more by handling all Aura exceptions from within the Apex Proxy.