11

Lightning component attributes are used to pass data into a component. The attribute can a basic type (string, number, boolean), object, collection, etc. I have a React background and am used to passing callbacks as component attributes. Is there a way to pass a function type into a Lightning component?

I'm thinking something like this:

<aura:attribute name="getSuggestions" required="true" type="Function" description="Callback to get the suggestions to display to the user" />

EDIT:

Here's an example of <ui:button />. The callback c.save is passed to the press attribute.

<ui:button label="Save" class="slds-button" press="{!c.save}"/>

Say I want to create my own implementation of <ui:button /> called myButton. I would never do this in practice, but it provides a good example of passing a callback to a child component.

In my custom button, I have declared three attributes: label, class, and press. I want to know how to implement the press attribute. The example below doesn't work.

myButton.cmp:

<aura:component>
    <aura:attribute name="label" required="true" type="String" />
    <aura:attribute name="class" required="false" type="String" />
    <aura:attribute name="press" required="true" type="Object" />

    <input type="button" value="{!v.label}" class="{!v.class}" onclick="{!c.onClick}" />
</aura:component>

myButtonController.js:

({
    onClick : function(component, event, helper) {
        // Do some cross-cutting action like track button clicks.
        helper.trackButtonClick(component, event);

        var press = component.get('v.press');
        press(); // This won't work because it's not a function. But it should be! I passed it a function!
    }
})

myButtonConsumer.cmp

<aura:component>
    <c:myButton label="OK" class="slds-button" press="{!c.onOk}" />
</aura:component>

myButtonConsumerController.js

({
    onOk: function() {
        alert('OK pressed!');
    }
})

I pass the function c.onOk to the press attribute, but when I invoke component.get('v.press'), it doesn't return a function. It seems like the Lightning framework wraps it somehow. How do I get at the inner value and execute it?

Or perhaps there's a better way to implement this?

  • 1
    Looks like there's an answer over here for your question. – Praveen Oct 12 '16 at 19:26
  • That answer only gets me part of the way there. If you pass press directly to the onclick attribute of input, then it works. But if you try to call press in the controller, then you get an error - "Press is not a function." – Joe B Oct 13 '16 at 14:31
15

The solution is to declare an event instead of an attribute when you want to provide a callback. Below is myButton.cmp rewritten to use events.

pressEvent.evt

<aura:event type="COMPONENT" />

myButton.cmp

<aura:component>
    <aura:attribute name="label" required="true" type="String" />
    <aura:attribute name="class" required="false" type="String" />

    <!-- Declare an "instance" of pressEvent -->
    <aura:registerEvent name="press" type="c:pressEvent"/>

    <input type="button" value="{!v.label}" class="{!v.class}" onclick="{!c.onClick}" />
</aura:component>

myButtonController.js

({
    onClick : function(component, event, helper) {
        helper.trackButtonClick(component, event);

        var event = component.getEvent('press');
        event.fire();
    }
})

This wasn't clear from the documentation, but there are two ways to handle an event. The first is to use the <aura:handler /> markup. To handle the press event of myButton, you could do this:

<aura:handler name="press" event="c:pressEvent" action="{!c.onOk}"/>

I would avoid this approach because if you have more than one button, the press event for all of them would be handled by the same action c.onOk.

The second way to handle an event is to pass a callback as an attribute in the <c:myButton /> markup. I didn't see this in the Salesforce docs but found it in an unrelated question. This is exactly what I needed. Each instance of myButton gets a unique callback.

myButtonConsumer.cmp

<aura:component>
    <!-- No aura:handler necessary. Just pass the callback as an attribute -->
    <!--<aura:handler name="press" event="c:pressEvent" action="{!c.onOk}"/>-->

    <c:myButton label="OK" class="slds-button" press="{!c.onOk}" />
    <c:myButton label="Cancel" class="slds-button" press="{!c.onCancel}" />
</aura:component>

myButtonConsumerController.js

({
    onOk: function(component, event) {
        alert('OK pressed!');
    },

    onCancel: function(component, event) {
        alert('Cancel pressed!');
    }
})
  • 2
    Since passing the callback is undocumented (and thus possibly unsupported) I'd like to offer a thought here: maybe it would work to have one generic 'onButtonPressed' action that would handle all button presses? That onButtonPressed action would then do different things based off an event parameter that would be set by the buttons that fired the event? – Willem Mulder Sep 5 '17 at 12:13
  • 1
    Here's the documentation of this feature. Handle Component Event of Instantiated Component – Scott McClung Feb 27 '18 at 22:39
3

Another solution will be using <aura:method>. The downside will be that you are not able to 'bind' a value from the parent markup (you have to call the method from a parent's JavaScript Helper) but maybe this technique is interesting for you anyway:

MyComp.cmp:

<aura:method name="doSth" action="{!c.onDoSth}">
    <aura:attribute name="callerComponent" type="Aura.Component" />
    <aura:attribute name="callback" type="Object" />
</aura:method>

MyCompController.js:

({
    onDoSth:function(component, event, helper) {
       var params = event.getParams().arguments;
       var callerComponent = params.callerComponent;
       var callback = params.callback;
       [do sth]
       if(!$A.util.isUndefinedOrNull(callback)) {
          callback(callerComponent, 'message from my comp');
       }
    }
})

ParentComponent.cmp:

<aura:component>
   <aura:handler name="init" value="{!this}" action="{!c.onInit}" />
   <c:MyComp aura:id="myComponent" />
</aura:component>

ParentComponentController.js:

({
   onInit:function(component, event, helper) {
      helper.callMyComponent(component);
   }
})

ParentComponentHelper.js:

({
    callMyComponent:function(component) {
        component.find("myComponent").doSth(component, myInternalCallback).bind(this);
    },

    myInternalCallback:function(component, returnMessage) {
        //back from other component
    }
})
  • i was working on different problem but the idea of passing component as param helped me, otherwise the component is not available during callback helper execution. – realnumber Mar 31 '18 at 19:13

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.