Extending Lightning Components by Example

Learn how to extend other lightning components using these examples, and how component inheritance increases developer productivity.

Guest post: Peter Knolle is a Solutions Architect at Trifecta Technologies, Force.com MVP, and Salesforce Developer Group Organizer.  He holds six Salesforce certifications, blogs regularly at peterknolle.com, and is active on Twitter @PeterKnolle.

The Lightning Component framework supports inheritance hierarchies through component extension. There are similarities to inheritance in Apex or other object-oriented programming languages like Java, but instead of applying to classes they apply to components. In some ways, the similarities can make learning about component inheritance easier to pick up if you have experience developing with an object-oriented programming language. However, there are differences and concepts specific to Lightning Component inheritance to keep in mind which will be covered in detail in the rest of this post. If you don’t have an object-oriented background, that’s perfectly fine, too, and you’ll have the added advantage of not having to frequently correct yourself from saying “sub class” and “super class” where you really mean sub component and super component.

Extending a Lightning Component

When a component extends another component it inherits all of the helper methods and attributes. It also has the ability to call super component controller actions, but don’t do it (more on that later). All event handlers in the super component are inherited by the sub component. Additionally, they can both handle the same event, but the ordering of handler execution is not defined (kind of like multiple Apex triggers on a single object).

When you want to create a component that can be extended you must set a value of true for the extensible attribute of the aura:component. By default, components are not extensible, just like Apex classes are not. As an example, consider a component, objectPanel, that displays some basic information about a Salesforce object in a uniform way, has a section that allows sub components to display a group of records however they wish, and also provides common methods to interact with the database in its helper. Since one of the main features is to show the records and operate on them and the objectPanel does not provide its own list of records, it is defined as abstract by specifying abstract=”true”.

<aura:component extensible="true" abstract="true" controller="ObjectPanelController">
    <aura:attribute name="sObjectType" type="String" required="true" />
    <aura:attribute name="maxRows" type="Integer" default="20" />
    <aura:attribute name="fields" type="String" />
    <aura:attribute name="records" type="Object[]" />
    <aura:attribute name="sObjectInfo" type="Object" />

    <aura:handler name="init" value="{!this}" action="{!c.doInit}" />
    <aura:handler event="aura:waiting" action="{!c.waiting}"
                  description="toggles spinner when data begins loading" />
    <aura:handler event="aura:doneWaiting" action="{!c.doneWaiting}"
                  description="toggles spinner when data finishes loading" />

    <h2>{!v.sObjectInfo.Label}</h2>
    <div>{!v.body}</div>
    <div>
        <a onclick="{!c.navigateToObjectList}">Visit {!v.sObjectInfo.LabelPlural}</a>
    </div>

</aura:component>

Here is an example of the objectPanel component being extended by the accountPanel component.

<aura:component extends="c:objectPanel">
    <aura:attribute name="showAccountWarning" type="Boolean" default="true" />

    <aura:set attribute="sObjectType" value="Account" />
    <aura:set attribute="fields" value="AccountNumber,Website" /> 

    <aura:iteration items="{!v.records}" var="rec">
        <div>
            <a onclick="{!c.deleteRecord}">Del</a> | 
            <a onclick="{!c.navigateToRecord}"><ui:outputText value="{!rec.Name}"/></a>
            <ui:outputText value="{!rec.AccountNumber}" />
            <ui:outputURL value="{!rec.Website}" />
            <aura:renderIf isTrue="{!and(v.showAccountWarning, rec.Is_Red__c)}">
                <span class="warn">WARNING: Red Account</span>
            </aura:renderIf>
        </div>
    </aura:iteration>
</aura:component>

The extends attribute is set to the c:objectPanel component to indicate that the accountPanel is a sub component of the c:objectPanel component. Note the “c:” that precedes the objectPanel in the extends attribute. It is the namespace of the component being extended, which in this case is the default namespace. If you were extending a component in a managed package you’d use its namespace.

Attribute Inheritance in Lightning Components

The attributes declared in the super component are available in the sub component to use and to set. The sObjectType and fields super component attributes are set with aura:set and the records super component field is iterated over. Additionally, the sub component has declared its own attribute, showAccountWarning.

You may have noticed the use of an attribute named body in the super component as {!v.body} and also noticed that there is not a declaration for it in any of the code. Where did it come from? The body attribute is inherited from the root level aura:component component from which all of the other components descend. The body is defined as all of the free markup in the component (i.e., not contained in <aura:set>, <aura:register>, etc).

Normal attributes that are inherited are shared between the super and sub component, so there is only one attribute with the same value, but the body attribute’s inheritance is different. The body attribute can have different values at different levels of the inheritance hierarchy. The way it works is that a sub component’s body is evaluated and passed to a super component to use as {!v.body} and that super component’s body is evaluated and is passed to its super component, and so on, until there’s no super component and the body is inserted into the document.body. In the example, body of the accountPanel consists of the aura:iteration and what it renders to is what is available in the {!v.body} used in the objectPanel.

Helper and Controller Inheritance in Lightning Components

The sub component has a link that it renders that executes its navigateToRecord controller method when it is clicked and a similar link to delete a record. The controller of the sub component is defined as:

({
    navigateToRecord : function(component, event, helper) {    
        helper.navigateToRecord(component);
    },
    deleteRecord : function(component, event, helper) {
        helper.deleteRecord(component);
    }
})

The objectPanel super component has a helper with the navigateToRecord and deleteRecord methods defined. The sub component just inherits it and can use it without needing any additional code. It does not even need to define its own helper at all. You may ask yourself, why can’t the super component define a controller method navigateToRecord that the sub component can simply use, thereby eliminating the need for the sub component to even define a controller. The answer is that it can…for now. However, don’t do it because it might be deprecated in the future. The controller functions are meant to handle events such as user interactions within the component itself. The helper is where all shared JavaScript should reside.

The super component defines a method in its helper to get the list of sObjects after the component is initialized.

loadList : function(component) {
    var cc = component.getConcreteComponent();
    cc.getDef().getHelper().preLoadProcess(cc);

    var action = component.get("c.getRecords"); 
    action.setParams({
        sObjectType: component.get("v.sObjectType"),
        fields: component.get("v.fields"),
        maxRows: component.get("v.maxRows")
    });

    action.setCallback(this, function(a) {
        if (a.getState() === "SUCCESS") {
            component.set("v.records", a.getReturnValue());
        } else if (a.getState() === "ERROR") {
            $A.log(a.getError());
        }
    });

    $A.enqueueAction(action); 
}

The loadList method provides a way for sub components to hook into it to perform any necessary processing prior to retrieving the records from the server-side action. By default, when a helper method is called in a super component, the super component’s implementation is used and not the overriding sub component’s. To do that, the concrete component must be retrieved (component.getConcreteComponent()) and its helper called. If the sub component does implement the preLoadProcess then its implementation will be called.

Interfaces in Lightning Components

A component can implement multiple interfaces, but extend only a single component. This is just like Apex which allows a class to implement multiple interfaces but only extend a single class. An interface can only declare attributes and cannot have anything else defined in it. Because interfaces are limited to attributes only and can’t have any code or other markup it is less likely that you’ll define your own in your custom components, but there are some use cases.

There are a couple of places where interfaces do make sense. One is the marker interface pattern which enables other code to process components that implement the marker in a certain way, being able to make assumptions about the components that implement it. Another is to provide a set of attributes in a standard way across components. For example, you might have a pagination interface that defines all of the attributes such as pageSize, pageNumber, hasNext, etc., that a component that implements pagination would support. JavaScript is loosely type and there is no concept of interfaces in it, but the JavaScript API in the Lightning Component framework provides a way to test if a component implements an interface. You can use the instanceOf method on the Component object to test if a component implements an interface, e.g., component.instanceOf(“c:iterable”).

Lightning Component Examples, Turorials and Resources

For more information on controllers in the Lightning Component Framework, refer to the Lightning Component Developer’s Guide. For a quick overview and introduction to Lightning Components, visit the Lightning Components page on Salesforce Developers. If you’re ready to learn hands-on while earning badges to demonstrate your skills, start the Lightning Components module in Trailhead.

Published
March 18, 2015

Leave your comments...

Extending Lightning Components by Example