The Lightning Component framework provides support for inheritance through component extension. This article describes how inheritance can be used in the framework. An example base component that displays standard record detail information and allows sub components to extend it to add their own information (e.g., Contact info, Opportunity info, etc.) is used to illustrate concepts.
Lightning Component inheritance is similar to object-oriented inheritance in programming languages like Apex or Java. When a sub component extends a super component it inherits the super component’s attributes, event handlers, and helper methods. The controller methods of the super component can be called by the sub component but the documentation warns not to do that and suggests that it may become deprecated. The recommended approach is to use the helper for any super component code a sub component needs to use. Additionally, abstract components and interfaces can be created.
Super Component
The example component uses an Apex controller, RecordController
, to get the specified record with the specified fields. It displays some standard fields and whatever is produced by the sub component (more on that later) . The component is named c:basicDetail
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<aura:component extensible="true" controller="RecordController"> <aura:attribute name="record" type="Object" description="The record being displayed."/> <aura:attribute name="standardFields" type="String" default="Id,Name,LastModifiedDate,LastModifiedBy.Name" description="These fields are always present."/> <!-- set by sub components --> <aura:attribute name="recordId" type="Id" description="The Id of the record" required="true" /> <aura:attribute name="objectType" type="String" description="The object type of the record"/> <aura:attribute name="fields" type="String" description="Fields for the object" default="Id,Name"/> <h1><ui:outputText value="{!v.record.Name}"/></h1> <!-- contents of the sub component --> <div>{!v.body}</div> <!-- Always show for every record --> <h2>System Information</h2> <ul> <li>Last Modified By: <ui:outputText value="{!v.record.LastModifiedBy.Name}" /></li> <li>Last Modified Date: <ui:outputDateTime value="{!v.record.LastModifiedDate}" /></li> </ul> </aura:component> |
The base component must specify that it can be extended by specifying the extensible=”true” attribute. The sub components could be used as follows:
1 2 3 4 |
<aura:application> <c:contactDetail recordId="003B0000001AADD2"/> <c:opportunityDetail recordId="006B00000098765"/> </aura:application> |
Sub Component
The sub component displays information from the super’s record attribute.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<aura:component extends="c:basicDetail"> <aura:handler name="init" value="{!this}" action="{!c.doInit}" /> <aura:set attribute="objectType" value="Contact" /> <aura:set attribute="fields" value="Email,Phone" /> <h2>Sub component</h2> <ul> <li>Name from super: {!v.record.Name}</li> <li>Email: <ui:outputEmail value="{!v.record.Email}"/></li> <li>Phone: {!v.record.Phone}</li> </ul> </aura:component> |
The sub component extends the base component by specifying the extends="c:basicDetail"
attribute. It uses aura:set attribute
to set values of attributes declared in the super component.
Initialization
The super component’s init event handler simply calls a method on the helper to load a record.
1 2 3 |
doInit : function(component, event, helper) { helper.loadRecord(component); } |
The loadRecord
method is responsible for getting the record from the DB via an Apex controller action, getObject
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
loadRecord : function(component) { var stdFields = component.get("v.standardFields"); var extFields = component.get("v.fields"); var fields = stdFields + (extFields != null ? ','+ extFields : ""); var action = component.get("c.getObject"); action.setParams({ recordId: component.get("v.recordId"), objectType: component.get("v.objectType"), fields: fields }); action.setCallback(this, function(a) { console.log(a.getReturnValue()); component.set("v.record", a.getReturnValue()); var cc = component.getConcreteComponent(); cc.getDef().getHelper().postGetHook(cc); }); $A.enqueueAction(action); } |
The loadRecord
helper method of the super component uses its attributes with default values and attributes that have values specified by the sub component to build the parameter list to the Apex controller action. The callback sets the record attribute which is available for the super and sub component and calls to a “hook” method that sub components can override.
Helper Inheritance
The helper methods can be overridden in the sub component by defining a method with the same name as the super component. Code in the sub component that calls the overridden method will then use the sub component’s implementation and not the super component’s. There is a slight difference for component inheritance vs. traditional OO in that when the overridden method is called from the super component’s code it calls the super component’s implementation and not the sub component’s. However, there is a way to code the super component so that the sub component’s method will get called if it has been overridden. There is a method in the JavaScript API, getConcreteComponent()
that returns the concrete component which can be used: component.getConcreteComponent().getDef().getHelper().someHelperMethod()
. In the example, the super component call to postGetHook()
will call the sub component’s implementation, if there is one. There is also a method to get the super component from the sub component context, component.getSuper()
.
Body Inheritance
In the super component markup some of the fields from the record are displayed and the sub component’s body is displayed as well. The body attribute is inherited from the root level aura:component
from which all components descend. The free markup in the component (i.e., not in aura:register
, aura:attribute
, aura:set
, etc.) is considered a part of the body. Technically, the free markup could be surrounded by an aura:set attribute="body"
, but it is not necessary as the framework treats all free markup as part of the body. The body inheritance works in a special way in that the value is different at each level in the inheritance tree. This is in contrast to normal attributes, where there only exists a single shared value, whether in sub or super component. The sub component’s body is evaluated and passed to the super component in the {!v.body}
attribute for the super component to use. It may use it and then its body is passed to its super component, and so on until there is no super component indicating it’s the root component and the body is inserted into the document body.
Wrap It Up
The inheritance mechanisms built into the Lightning Framework are elegant and powerful. This article gave an introduction to Lightning Component inheritance, but there is much more that can be done. To learn more about inheritance in the Lightning Component framework read the Lightning Developer’s Guide. Additionally, the reference.app is available directly in your org at https://<yourinstance>.lightning.force.com/auradocs/reference.app, e.g., https://na15.lightning.force.com/auradocs/reference.app. You can also get to it from a simpler URL of https://<yourinstance>.salesforce.com/auradocs, e.g., https://na15.salesforce.com/auradocs.
Great article! Thanks
Hi,
Since LockerService activation, it’s not possible anymore to access the helper of the inheriting component using getDef().getHelper()
Do you have a workaround ?
Thanks & best regards
Hi,
Any workaround to replace getDef() since it is not working with locker service activated?
Thanks!
I found a way using aura:method 🙂
SalesForce people didn’t validate or deny the solution for the moment, but it works with LockerService 🙂
–> http://salesforce.stackexchange.com/questions/118270/lightning-lockerservice-and-component-inheritance
Best regards
Here is the workaround I am testing. The downside is controller action inheritance is something that Salesforce also wants to remove from lightning. Why are they so intent in crippling their own framework?
In your super component controller’s init action, add a handler on the concrete component like so:
component.addHandler(‘onBindComponent’, component.getConcreteComponent(), ‘c.onBindComponent’);
Fire the event on the next tick:
setTimeout(component, $A.getCallback(function(){
component.getEvent(‘onBindComponent’).fire();
}), 0);
Then in the parent controller, add a default handler like so:
onBindComponent: function(cmp, event, helper) {
if (!event.getSource().getGlobalId() !== component.getGlobalId()) return; //only handle our own events
event.stopPropagation(); // stop event from propagating to other components
// helper === subcomponent helper!
}
Finally register the event in your super component’s markup
Is component.getSuper() part of the API?
I don’t see any documentation for this.
Very nice article, i made my first inherited lightning component based on this one! However its slightly outdated: component.getDef().getHelper does not exist anymore. I worked around this by firing an event on the super and handling it on the sub component.
https://salesforce.stackexchange.com/questions/194230/lightning-component-polymorphism-is-not-working