7

I posted this question on Twitter and folks seemed to feel this wasn't expected behavior, so posting here to see if others have experienced\have better solution

I have three components. CMP A is a simple component that uses force:recordData so I can display on Contact detail page. On record update of CMP A (force:recorddata is async, so we can't use init) it creates CMP B which queries an apex controller to get back some values from a custom object for the Contact, and displays a select list of those values. That works fine. On change of the select, I create CMP C, which is using values from CMP B to create a simple table:

var finalbody = [];

$A.createComponent(
        "c:ProgramListSelector",
        {
            "program" : programsToDisplay,
            "programMap" : component.get("v.programMap")
        },
        function(newListCMP, status, errorMessage){
            //Add the new component to the body array
            if (status === "SUCCESS") {
                finalbody.push(newListCMP);
               console.log('final body' + finalbody);
               var parentCmp = component.find("newLists");
               parentCmp.set("v.body", finalbody); 
            }
            //Error Handling
        }
       );

     }

If I look in the console, the first time I do this the success statement appears after that console.log('final body' + finalbody) statement and the component is not added to the page. Change the select again, and it works correctly every subsequent time. The only way I found to correct it was to add CMP C as an aura dependency in CMP B - once I did that, it always worked, even the first time. But adding it is obviously a performance hit, so it feels like I shouldn't need to enforce that dependency? One other thing that might be a factor - CMP C references an Apex Wrapper class - could that be why I need to force a dependency?

    public with sharing class ContactListWrapper {

        @AuraEnabled
        public Boolean bSelected {get;set;}
        @AuraEnabled
        public String ListName {get;set;}

        public ContactListWrapper(List__c Listso, Boolean b) {
            pList = Listso;
        }
}
  • Two things you could try in debugging - remove controller reference to see if the cmp renders. console.log the error message on failure... Oh so no Apex controller dependency in CMP C? Also, are you sure it's a performance hit to have the depencency? Not having it requires a server round trip to get the cmp def. – Caspar Harmer Jun 5 '18 at 18:25
  • Correct - CMP C doesn't make any calls to Apex controller - it's using data passed into it. BUT - it is using fields in this wrapper class which is an attribute in CMP C, so based on twitter convo, looks like CMP makes an async call first time to get definition, and then once it has it, it can make sync call and that's why subsequent calls work. – BritishBoyinDC Jun 5 '18 at 18:59
  • Maybe define that component attribute as a simple map or similar rather than a typed Apex class? – Caspar Harmer Jun 5 '18 at 21:05
4

So I had a great conversation with Kris Gray about this issue, and the core problem I was hitting is that the initial creation of my component is happening asynchronously because it has to retrieve the definition from the server. After that, it has the definition in the local cache, so it will happen synchronously.

In order to handle that possibility, he suggested I change my code to set the body variable of the component directly, rather than use a local variable. That way, it doesn't matter when they component creation is completed - it will just appear in the component body once the callback is completed.

The difference is pretty subtle, but it did mean I could remove the aura-dependency tag and it still worked. More generally, the big takeaway for me was more actions that I realized were happening async and I need to do a better job of handling that possibility

  1. retrieve the body of the div from the parent component we are updating and set to empty so we can refresh when parent selector changes
  var parentCmp = component.find("newLists");
  parentCmp.set("v.body", []);

             $A.createComponent(
            "c:ProgramListSelector",
            {
                "program" : programsToDisplay[y],
                "programMap" : component.get("v.programMap")

            },
            function(newListCMP, status, errorMessage){
                //Add the new component to the body array
                if (status === "SUCCESS") {

Get the body of the parent body into a local variable and update that var directly

               var localBody = parentCmp.get("v.body");
                localBody.push(newListCMP);

Now set the body of parent to the local var instead of just adding it to a local variable and adding it at the end

                    parentCmp.set("v.body", localBody);
                }
| improve this answer | |

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.