I have a lightning component that will display a long list of contacts (could be thousands), with a nested data table from a related object. That related data is editable, so I'll want to be able to update the Contact info of a single contact without doing a SOQL on all the contacts. So, while my component is iterating on a List, I want to create a Map.
COMPONENT
<aura:attribute name="Contacts" type="List" default="[]"/>
<aura:attribute name="ContactsMap" type="Map" default="{}"/>
My Apex controller is returning List as expected, done in batches using OFFSET, triggered by scroller in the Renderer. So, with each batch, I add the 30 newly retrieved records to the Contacts list, and I need to add them to the Map as well.
In my CONTROLLER, I'm saving the getReturnValue to var contacts, and using push.apply to append that list to the existing Contacts list. My problem happens when I try to put the contacts into my map. It gives me this error:
Uncaught Error in $A.getCallback() [ContactsMap.set is not a function]
Callback failed: apex://ContactController/ACTION$getContacts
Here's the code:
CONTROLLER
getData : function(cmp, event, helper)
{
var moreData = cmp.get("v.moreData");
if (moreData)
{
var spinner = cmp.find("spinner");
$A.util.toggleClass(spinner, "slds-hide");
var action = cmp.get("c.getContacts");
var offset = cmp.get("v.offset");
action.setParams({
"offset": offset
});
action.setCallback(this, $A.getCallback(function (response) {
var state = response.getState();
if (cmp.isValid() && state === "SUCCESS"){
var contacts=response.getReturnValue();
var ContactList = cmp.get("v.Contacts");
ContactList.push.apply(ContactList, contacts);
cmp.set('v.Contacts', ContactList);
$A.util.toggleClass(spinner, "slds-hide");
var offsetI = parseInt(offset);
offsetI += 30;
cmp.set('v.offset', offsetI.toString());
cmp.set("v.moreData", (!cmp.get("v.totalContacts")<offsetI));
// push to map
var ContactsMap = cmp.get("v.ContactsMap");
for(var i in contacts)
{
ContactsMap.set(contacts[i].Id, contacts[i]);
}
cmp.set("v.ContactsMap", ContactsMap);
} else if (state === "ERROR") {
var errors = response.getError();
console.error(errors);
}
}));
$A.enqueueAction(action);
}
},
What am I missing?
UPDATE: Thanks for the help on this one. In my lightning component, I needed both a map (for inserting updates) and a list (for aura:iteration) of the data. My question was assuming that SOQL returned a list, and then I'd create the map in the JS controller. I ended up reversing that.
My Apex SOQL query returns the Map (which is as simple as getting the list). Then, in my JS controller, I get the list from the map with just a line:
var contactsMap = response.getReturnValue();
var contactsList = Object.values(contactsMap);
Best Answer
You're not missing anything. The documentation is misleading and/or wrong for this one.
The default
{}
is actually a JS Object notation.The below workaround works, and you're essentially using the JavaScript Map.prototype at this point. The type parameter doesn't seem to do anything for the Map type even in API v43. Maybe they'll fix it later?
P.S. This is why a JS Object is more widely used, they already have key:value pairs and you can still have a
get()
just by usingobject[key]
orobject["specificKey"]
or evencomponent.get("v.myJsObject")["mySpecificKey"]
.