[SalesForce] Lightning component controller can’t set pairs into attribute of type Map

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?

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


...


let rowIdToRowMap = new Map();
for (let row of data) {
    rowIdToRowMap.set(row.Id, row);
}
component.set("v.rowIdToRowMap", rowIdToRowMap);


... and for usage later ...


let rowIdtoRowMap = component.get("v.rowIdToRowMap");
for (let key of rowIdToRowMap.keys()) {
    console.log(key) //outputs row.Id
    console.log(rowIdToRowMap.get(key)) //outputs row
}

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 using object[key] or object["specificKey"] or even component.get("v.myJsObject")["mySpecificKey"].

Related Topic