3

I have a bunch of components <c:CmpA />, <c:CmpB /> ...

Sometimes, I need to perform a check for access rights to this component, so I decided to create a <c:CheckWrapper></c:CheckWrapper> as this:

<aura:component description="CheckWrapper"
                controller="CheckWrapperController"
>
  <aura:if isTrue="{! not(v.loading) }">

    <aura:if isTrue="{! v.isAllowed }">
    {! v.body }
      <aura:set attribute="else">
        {! v.error }
      </aura:set>
    </aura:if>

  </aura:if>
</aura:component>

Its JS controller perform a check on Apex, and then release v.isLoading and v.isAllowed (or not).

The usage is has this:

<c:CheckWrapper somethingToCheck="['this', 'that']">
  <c:CmpA attr="{! v.attr }" />
</c:CheckWrapper>

Everything works perfectly except that even if you are not allowed to use this component (and it will not be mounted on the DOM because of <aura:if isTrue="{! v.isAllowed }">), it is instantiate and its <aura:handler name="init" value="{!this}" action="{!c.doInit}"/> is performed.

How can I prevent this without dynamically creating the component ? As an example, <lightning:Tab> and <lightning:Tabset> work exactly the same way but the child component's init handler is performed on render.

<lightning:tabset selectedTabId="{! v.stepName }" >
  <lightning:tab label="label" aura:id="id" id="id" >
      <c:CmpA attr="{! v.attr }" />
      <!-- Tab is loaded, component is mounted, init is performed -->
  </lightning:tab>
  <lightning:tab label="label2" aura:id="id2" id="id2" >
      <c:Cmpb attr="{! v.attr }" />
      <!-- Tab is hidden, component is unmounted, init is not performed until open the tab -->
  </lightning:tab>
</lightning:tabset>

Do you have any idea I can have the same behaviour ?

0

Can you try this inside your CheckWrapper Component.

<aura:if isTrue="{! v.isAllowed }">
    <c:CmpA attr="{! v.attr }" />
    {! v.body }
    <aura:set attribute="else">
    {! v.error }
</aura:set>
</aura:if>

Edit Solution 2:

Here you can pass your component as an attribute to checkWrapper and let checkWrapper decide whether to show or not components.

Test App Code

<aura:application >
    <c:CheckWrapper componentName="c:CmpA"></c:CheckWrapper>
</aura:application>

CheckWrapper.cmp

<aura:component description="CheckWrapper" controller="CheckWrapperController">
<aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
<aura:handler name="change" value="{!v.isAllowed}" action="{!c.isAllowedChanged}" />

<aura:attribute name="somethingToCheck" type="String"></aura:attribute>
<aura:attribute name="componentName" type="String"></aura:attribute>
<aura:attribute name="component_attributes" type="String"></aura:attribute>
<aura:attribute name="isAllowed" type="Boolean" default="false"></aura:attribute>

<aura:if isTrue="{! not(v.loading) }">

<aura:if isTrue="{! v.isAllowed }">
        {! v.body }

        <aura:set attribute="else">
        {! v.error }
      </aura:set>
    </aura:if>
</aura:if>
</aura:component>

CheckWrapperController.js

doInit : function(component, event, helper) {
    var show = component.get("v.isAllowed");
    if(show)
    helper.createCmp(component,event);

},
isAllowedChanged : function(component, event, helper) {
    var show = component.get("v.isAllowed");
    if(show)
    helper.createCmp(component,event);
}

CheckWrapperHelper.js

createCmp : function(component,event) {

    var cmpName= component.get("v.componentName");
    var cmpAttr = component.get("v.component_attributes");
    var cmp = component;
    $A.createComponent(cmpName, cmpAttr, function(newComp, status, errorMessage){

            if (status === "SUCCESS") {
                var body = cmp.get("v.body");
                body.push(newComp);
                cmp.set("v.body", body);
            }

        });
}

CmpAController.js

doInit : function(component, event, helper) {
    console.log('comp A initiated');
}

CmpA.cmp

<aura:component >
    <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
    <h1>I'm Component A</h1>
</aura:component>

You can change the default value of isAllowed to true on checkWrapper to check component creation. Hope this helps!!

4
  • This works but the purpose is to not specify component inside the checkWrapper so it can be used for every component that requires a check
    – Simonp
    Mar 9 '18 at 9:56
  • About your edit: this is a really good idea and it could work where the attribute binding is not used but I need to be able to do <c:CmpA attr="{! v.valueThatCouldChange }>
    – Simonp
    Mar 9 '18 at 14:12
  • I think it should work fine. Make a Parent Component and CheckWrapper as child component. As the components are nested inside checkWrapper they should be binded to any attribute present on parent component Mar 9 '18 at 14:36
  • i'll try to see if i could improve it. @SimonPlacentino Mar 9 '18 at 15:05
0

So, I followed the (perfect) answer of pranav prashant but I am facing a new issue. First, here is the entire implementation I have.

Markup:

<aura:component description="CheckWrapper"
                controller="CheckWrapperController"
>

  <aura:attribute name="isAllowed"    type="Boolean" access="private" default="{! false }" />
  <aura:attribute name="loading"      type="Boolean" access="private" default="{! true }" />

  <aura:attribute name="error"        type="Aura.Component[]" access="public"/>

  <aura:attribute name="bodyAttributes"     type="Map"     />
  <aura:attribute name="bodyDefinition"     type="String" required="{! true }" />
  <aura:attribute name="bodyContent"        type="Aura.Component[]" access="private"/>

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

  <aura:if isTrue="{! not(v.loading) }">

    <aura:if isTrue="{! v.isAllowed }">
    {! v.bodyContent }
      <aura:set attribute="else">
        {! v.error }
      </aura:set>
    </aura:if>
  </aura:if>
</aura:component>

Controller + Helper:

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

})

({
  checkAccess: function(component) {
    const action = component.get('c.hasAccess');
    action.setCallback(this, response => {
      // if success
        component.set('v.isAllowed', allowed);

        if(allowed) {
          this.createComponent(
            component,
            component.get('v.bodyDefinition'),
            component.get('v.bodyAttributes') || {}
          );
        }
    });

    $A.enqueueAction(action);
  },

  createComponent: function(component, bodyDefinition, bodyAttributes) {
    $A.createComponent(bodyDefinition, bodyAttributes, (body, status, errorMessage) => {
      if (status === "SUCCESS") {
        component.set('v.bodyContent', body);
      } else {
        console.error(errorMessage);
      }

      component.set('v.loading', false);
    });

  }
})

Then I create a *WithPermission wrapper where it is required.

<aura:component implements="force:appHostable"
                description="CmpAWithPermission"
                access="global"
>

  <aura:dependency resource="markup://c:CmpA" />
  <aura:dependency resource="markup://c:CheckWrapper" />

  <c:CheckWrapper bodyDefinition="c:CmpA">
    <aura:set attribute="error">
      <p>Woups</p>
    </aura:set>
  </c:CheckWrapper>
</aura:component>

In case where wrapped component needs some attributes, I add to my *WithPermission component an attribute as this:

  <aura:component description="CmpBWithPermission"
                  access="public"
                  implements="force:hasRecordId,flexipage:availableForRecordHome">

    <aura:attribute name="cmpBAttributes"     type="Map"  />

    <aura:dependency resource="markup://c:CmpB"   type="COMPONENT" />

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

    <c:CheckWrapper bodyDefinition="c:CmpB" bodyAttributes="{! v.cmpBAttributes}"/>

</aura:component>

With controller:

({
  doInit: function(component, event, helper) {

    component.set('v.cmpBAttributes', {
      recordId: component.get('v.recordId') // for exemple
    });
  }
})

But now the new issue I am facing is when one of this *WithPermission component is used as a tab content (setup -> search(tab) -> Lightning Component Tab), when I switch to another tab and come back to te previous one, my component exist multiple time on the page:

<article class="slds-card cCheckWrapper" data-aura-rendered-by="50:1530;a" data-aura-class="cCheckWrapper">
<!-- The last one, content OK -->
</article>
<article class="slds-card cCheckWrapper" data-aura-rendered-by="50:1530;a" data-aura-class="cCheckWrapper">
<!-- The second time I browse the component, empty: NOK -->
</article>
<article class="slds-card cCheckWrapper" data-aura-rendered-by="50:1530;a" data-aura-class="cCheckWrapper">
<!-- The first time I browse the component, empty: NOK -->
</article>

I do not get why because the component, even dynamic, is autoDestroyed because assigned to Aura.Component[] type (as described in the documentation).

1
  • Problem solved, CmpA was like <p>...</p><lightning:card title=""></lightning:card> then only <p> was deleted. I wrapped everything together
    – Simonp
    Mar 12 '18 at 9:43

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.