5

I have a struggle to understand something with Lightning here. Let me explain what I'm trying to do.

I have a component (let's call it ParentComponent) which has one main attribute -> object with nested objects within - attributes. I'm using this list of attributes in aura:iteration, where inside of it I'm using child lightning component (ChildComponent) - I just want to separate piece of functionality into smaller bricks.

<aura:component controller="PrdConfigCls">
  <aura:attribute name="paramProductId" type="String"/>
  <aura:attribute name="product" type="PrdConfigCls.Product"/>
  <aura:handler name="change" value="{!v.paramProductId}" action="{!c.paramProductIdChangeEventHandler}"/>

  <aura:iteration items="{!v.product.attributes}" var="attr">
      <c:ChildComponent attributeParam="{!attr}"/>
  </aura:iteration>

</aura:component>

Now everything works fine when loading the component for the first time. ChildComponent content properly renders attr objects passed to it, for all elements in the Product.attributes list.

But ParentComponent have productId which may change. If this happens I'm using calling APEX class to reload second attribute -> Product via standard mechanism:

ParentComponent

<aura:handler name="change" value="{!v.paramProductId}" action="{!c.paramProductIdChangeEventHandler}"/>

ParentComponentController

({
    paramProductIdChangeEventHandler : function(component, event, helper) {
        var paramProductId = component.get("v.paramProductId");
        if (paramProductId != null && paramProductId != '') {
            helper.loadProductConfiguration(component);
        }
    }
})

ParentComponentHelper

({
    loadProductConfiguration : function(component) {

        var action = component.get("c.getProjectProductDefinition");
        action.setParams({  "projectProductId": component.get("v.paramProductId") });
        var self = this;

        action.setCallback(this, function(a) {
            var productResp = a.getReturnValue();
            var product = JSON.parse(productResp);
            component.set("v.product", product);
        });
        $A.enqueueAction(action);

    }
})

Now, because this happens Product attribute in ParentController is changing, new Product have different list of Attributes so I would want content of <aura:iterable> to change - but unfortunately it doesn't happen. Instead I'm being left with a blank screen.

I tried to debug it and here is what happens (copied from chrome console)

1st change - loading the product for the first time:

-- Object {productDefinition: Object, product: Object, createComponentId: null, components: Array[2], attributes: Array[11]}
-- "ParentComponent": Done Rendering
-- 11x "ChildComponent": Done Rendering

2nd change - Done Rendering events are not happening - I only have output of console.log(product);

-- Object {productDefinition: Object, product: Object, createComponentId: null, components: Array[0], attributes: Array[21]}

Can you help me understand why it's not working? Maybe I'm just missing some rerender of child components?

I think I had this problem in the past when I discovered that attribute propagation to children works fine if you use Strings instead of complex types. It quite surprising to me, as in Visualforce Components it was totally opposite. I'm not very deep into how this works, but I think that in Visualforce Components attributes for complex types were passed through reference.

It would be great help if you can help me to understand this prior adding String serialization / deserialization magic.

2 Answers 2

4

I actually found a solution (or a workaround - still don't know). I will put it here in case somebody will come across same issue.

I had to dynamically recreate child component - therefore I used ChildComponentWrapper:

How it looks right now:

ParentComponent

<aura:component controller="PrdConfigCls">
  <aura:attribute name="paramProductId" type="String"/>
  <aura:attribute name="product" type="PrdConfigCls.Product"/>
  <aura:attribute name="attributes" type="PrdConfigCls.Attribute[]"/>
  <aura:handler name="change" value="{!v.paramProductId}" action="{!c.paramProductIdChangeEventHandler}"/>

 <div aura:id="container">     
 // in this place ChildComponentWrapper will instantiate after change event (helper)
 </div>

</aura:component>

ParentComponentHelper

({
    loadProductConfiguration : function(component) {

        var action = component.get("c.getProjectProductDefinition");
        action.setParams({  "projectProductId": component.get("v.paramProductId") });
        var self = this;

        action.setCallback(this, function(a) {

            var productResp = a.getReturnValue();
            var product = JSON.parse(productResp);
            component.set("v.product", product);
            component.set("v.attributes", product.attributes);

            //console.log(component.get("v.attributes"));

            var container = component.find("container");
            $A.createComponent("c:ChildComponentWrapper",
                           {attributeParam: product.attributes},
                           function(cmp) {
                               container.set("v.body", [cmp]);
                           });

        });
        $A.enqueueAction(action);

    }
})

ChildComponentWrapper

<aura:component controller="PrdConfigCls">
  <aura:attribute name="attributeParam" type="PrdConfigCls.Attribute[]"/>

  <aura:iteration items="{!v.attributes}" var="attr">
     <c:ChildComponent attributeParam="{!attr}"/>
  </aura:iteration>
</aura:component/>
2
  • i had the exact same problem, the child attributes are not refreshed automatically and we are having to dynamically create components , i couldnt find any function similar to reactjs's componentWillRecieveProps inside lightning components.
    – Sid
    Dec 24, 2016 at 12:46
  • Thanks a lot! This is still an issue in Summer17, although my encountered error was slightly different. The solution/workaround applied nevertheless. My problem was: Upon removing elements from my array (one, several or all did not matter), Aura threw an exception when certain ui:inputXXX components within my childComponent were rerendered. The only solution was to recreate the container on each change.
    – Christoph
    May 18, 2017 at 17:00
0

I found out, that lightning listens top level object change. I mean, if you have Object and it changes, lightning will react on it, but if you have Object[] and change element of this array, lightning will not react automatically.

So what im doing, once one of the elements is changing, im updating Object[] with:

component.set("v.array", component.get("v.array"));

In you situation you are iteration on second level of you attribute(parent attribute is first level) and i suppose lightning behaves in the same way.

So i would suggest to split your attribute to make your children also first level and update its attribute.

So create additional attribute:

<aura:attribute name="paramProductId" type="String"/>
<aura:attribute name="product" type="PrdConfigCls.Product"/>
<aura:attribute name="children" type="Object[]"/>

Then in your request just update children attribute along with parent:

var productResp = a.getReturnValue();
var product = JSON.parse(productResp);
var children = product.attributes;

component.set("v.product",  product);
component.set("v.children", children);

Then it should work, but yeah its sad, that lightning is still far away from angular/react and for proper work of one-two-way binding and rerendering we have to create a lot of unnecessary code.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .