Lightning LockerService and Component Inheritance

LS itself has no direct impact on inheritance or other features in the lightning framework. It does however enforce existing visibility and access rules that previously were unenforceable.

As we've gone through the process of developing the service's secure virtual proxies we have identified a number of places where things were documented but actually not marked correctly as part of our public documentation and in those cases we've actually corrected the documentation. we have not removed access to something that was marked as part of the publicly supported API (unless it represented and unsupportable security risk of course - e.g. Document.innerHTML's getter).

The specific API change you are talking about above occurred long prior to locker service and we've been working off of the current set of classes and methods marked with the @platform annotation that indicates that they are part of the publicly supported, API version and surface area. I do know that we never intended for that incantation to be a public part of Lightning.

I am researching now to get more information about the nature of the change with regard to Component.getDef() from the folks that made the change a few releases ago. The pattern your using looks like https://en.m.wikipedia.org/wiki/Template_method_pattern, correct? We do need a supported way to implement that very useful pattern. For example instead of helpers what about using an interface with aura:method's that can be implemented in the subs instead (I have not verified this works yet - just an idea)? Helpers are meant to be an internal component detail and the fact that these are "inherited" has never been treated as part of the API - was always a hack or workaround at best.

Also please note that I fully expect the summer 16 lifecycle to include many additional updates to increase what is visible in the locker service. For example the next patch schedule for deployment tomorrow has over 50 additional APIs visible primarily in the secure virtual DOM. Some of these are because we discovered even more inconsistencies between what is marked as platform versus exported. Each and every method, class, property etc. has to undergo a very range of threat analysis depending on the complexity that is being proposed for exposure.


So, after a lot of struggle, I think I found a solution so Template components can work with Locker Service and call helpers of components who instanciate them , using aura:method but without having to describe 10000 times each method.

To do that, the template component must inherit from a component CmpTemplateRoot , as described in the post.

Even if it seems to works correctly, my worries are :

  • I feel it's dirty to use a component attribute as buffer for function return value

  • I use inheritance on Cmp JS controller: in the doc it says not to do it, so the day it's killed, my solution won't work anymore.

  • I really would prefer a clean solution directly in the framework to access child helper from template component code, is it really impossible for SalesForce to implement that ? Doug Chasman u said "The pattern your using looks like https://en.m.wikipedia.org/wiki/Template_method_pattern, correct? We do need a supported way to implement that very useful pattern. " ... isn't there other ways to use it, with a detailed example ?

CmpTemplateRoot: to just copy-paste in your org, then if you make your components inherit from it, you can call method callConcreteCmpHelper: (component,HelperFunctionName,HelperFunctionParams) from a Template component helper to access child component helper methods.

Cmp:

<aura:component extensible="true" abstract="true" access="global" >

    <aura:method name="callCmpHelper" action="{!c.callCmpHelper}">
        <aura:attribute name="HelperFunctionName" type="String" />
        <aura:attribute name="HelperFunctionParams" type="Object" />
    </aura:method>  

    <aura:attribute name="HelperMethodResultList" type="Object" description="Buffer parameter to store instanciated component helper methods result"/> 

    {!v.body}
</aura:component>

JS controller:

({
    // NVU: Template component calling the instanciated component helper
    // The "this" of this method is the component instanciation of the template component
    callCmpHelper : function(component, event, helper) {
        var params = event.getParam('arguments');
        var HelperFunctionName = params.HelperFunctionName ;
        var HelperFunctionParams = params.HelperFunctionParams ;
        var HelperFunctionResult = helper[HelperFunctionName].apply(helper,HelperFunctionParams);
        // As aura:method can not return value, store return value in "buffer" attribute HelperMethodResultList.
        // Will be retrieved by template component method callConcreteCmpHelper
        if (HelperFunctionResult != null)
        {
            var HelperMethodResultList = component.get('v.HelperMethodResultList');
            if (HelperMethodResultList == null)
                HelperMethodResultList = {};
            HelperMethodResultList[HelperFunctionName] = HelperFunctionResult;
            component.set('v.HelperMethodResultList',HelperMethodResultList);    
        }
    }
})

JS Helper:

({
    // NVU: This method can be called to access helper methods of inherited components
    // The "this" of this method is the template component
   callConcreteCmpHelper: function (component,HelperFunctionName,HelperFunctionParams) {
        var ConcreteComponent = component.getConcreteComponent();
        ConcreteComponent.callCmpHelper(HelperFunctionName,HelperFunctionParams);
        // Get return value in "buffer" attribute
        var HelperMethodResultList = component.get('v.HelperMethodResultList');
        if (HelperMethodResultList == null)
            HelperMethodResultList = {};
        if (HelperMethodResultList[HelperFunctionName] != null)
            return HelperMethodResultList[HelperFunctionName] ;
    }
})

Here is an example of using :

CmpDummyRoot "Template component"

Cmp

<aura:component extensible="true" abstract="true" extends="irm:CmpTemplateRoot" access="global">

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

    <aura:attribute name="DummyVal" type="String" />

    My template component CmpDummyRoot displays the value <strong>"{!v.DummyVal}"</strong> returned by child component CmpDummyChild instanciating this template component
</aura:component>

JS Controller:

({
    doInit : function(component, event, helper) {
        helper.myMasterMethodHlp(component,event);
    }    
})

JS Helper:

({
    myMasterMethodHlp : function(component,event) {
        var DummyVal = this.callConcreteCmpHelper(component,'myChildMethodHlp',[component,event]);
        component.set('v.DummyVal',DummyVal);
    }
})

"Child component using the template component" CmpDummyChild

Cmp:

<aura:component extends="irm:CmpDummyRoot">

</aura:component>

Helper:

({
    myChildMethodHlp : function(component,event) {
        var ChildDummyVal = 'ValComingFromChildHelper!!';
        return ChildDummyVal;
    }
})