5

I am creating this Lightning component:

<aura:component>
  <aura:attribute name="record" type="Account" />
  <aura:attribute name="recordList" type="Account[]" />
   ...
</aura:component>

..dynamically using...

 $A.createComponent(
    "c:myComponent",
    {
        "record": cmp.getReference("v.account"),
        "recordList": cmp.getReference("v.accountList")
    },
);

I am perfectly able to access the single record but unable to access the record list.

myMethod: function(cmp) {
   cmp.get("v.record").Name = "Acme";  // Works

   for(var record of cmp.get("v.recordList")) {
      record.Name = "Acme";  // Fails
   }
}

Accessing the recordList fails with:

Uncaught Action failed: myComponent$controller$myMethod [Cannot add property Name, object is not extensible]

When I console.log both attributes v.record is a Map representing an SObject but the recordList is an obfuscated "thing"

Proxy {0: XI, 1: XI, 2: XI, 3: XI, 4: XI, 5: XI, 6: XI, 7: XI, 8: XI, 9: XI, length: 10}
[[Handler]]
:
Object
[[Target]]
:
Array(10)
0
:
XI {path: Array(3), Ja: "v.recordList.a0246000005mfQzAAI", Kd: false, Ga: a, pb: a, …}

Strangely we could make it work by temporarily assigning/casting it to another attribute of type Account.

for(var record of cmp.get("v.recordList")) {
    cmp.set("v.tempRecord", record);

    var temp = cmp.get("v.tempRecord");
    temp[field] = number;
    cmp.set("v.tempRecord", temp);

    cmp.clearReference("v.tempRecord");
}

Why that? A bug? By design?

5
  • 2
    Interesting question, have you tried treating the recordList like an array? e.g. iterate over it by doing cmp.get("v.recordList").forEach(function(record){record.Name = "Acme";}) for...of treats it as an iterable, rather than an array, and could possible be the source of the odd results.
    – Aidan
    Dec 8, 2017 at 11:53
  • Using forEach compared to for of didn't change a thing. Dec 8, 2017 at 12:37
  • 1
    I think this is LockerService related based on the error but not sure why it's locked down. Try using Account[] as the recordList type instead of List. I've had better luck with arrays than lists in the Lightning framework. Dec 8, 2017 at 21:27
  • According to developer.salesforce.com/docs/atlas.en-us.lightning.meta/… LockerService seems to obfuscate any HTMLCollection type to an obfuscated Proxy. But why and how do I then access collections? Dec 9, 2017 at 21:38
  • @RobertSösemann What release did you test this on? I tried to reproduce the issue on Winter 18 and the upcoming Spring 18, the assignment inside the for loop works fine. Will post my repro steps as an answer because there is limited space here. Dec 10, 2017 at 20:12

2 Answers 2

1

Seems to work fine on Winter 18 and Spring 18(upcoming) releases. Here's the code I used to repro the issue.

Apex Controller

public with sharing class AccountController {
    @AuraEnabled
    public static List<Account> findAll() {
    return [SELECT id, name
            FROM Account
            LIMIT 50];
    }
}

App code

<aura:application controller="AccountController">
    <aura:attribute name="accounts" type="Account[]"/>
    <aura:attribute name="account" type="Account" />
     App
    <aura:handler name="init" value="{!this}" action="{!c.doInit}" />
    <c:containerCmp account="{!v.account}" accountList="{!v.accounts}"/>
</aura:application>

App Controller

({
    doInit: function(component, event) {
        var action = component.get("c.findAll");
        action.setCallback(this, function(a) {
            component.set("v.accounts", a.getReturnValue());
            component.set("v.account", a.getReturnValue()[0]);
        });
        $A.enqueueAction(action);
    }
})

c:containerCmp

<aura:component>
  <aura:attribute name="account" type="Account" />
  <aura:attribute name="accountList" type="Account[]" />
  <aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
    container cmp
    {!v.body}
</aura:component>

c:containerCmp controller

({
    doInit : function(cmp) {
        $A.createComponent(
            "c:myComponent",
            {
                "record": cmp.getReference("v.account"),
                "recordList": cmp.getReference("v.accountList")
            },
            function(newInnerCmp, status, errorMessage) {
                if (status === "SUCCESS") {
                    var body = cmp.get("v.body");
                    body.push(newInnerCmp);
                    cmp.set("v.body", body);
                }
            }
        );
    }
})

c:myComponent

<aura:component>
  <aura:attribute name="record" type="Account" />
  <aura:attribute name="recordList" type="Account[]" />
  <ui:button press="{!c.clickHandler}" label="Show account details"/>
  <br/>
  <aura:iteration items="{!v.recordList}" var="recordd">
      {!recordd.Id} : {!recordd.Name} <br/>
  </aura:iteration>
</aura:component>

c:myComponent controller

({
    clickHandler : function(cmp, event, helper) {
        cmp.get("v.record").Name = "Acme";  // Works
        for(var record of cmp.get("v.recordList")) {
            record.Name = "Acme";  // Suppose to fail here, but works fine
        }
        // Optional step to reflect the mutation in the UI
        // cmp.set("v.recordList", cmp.get("v.recordList"));
    }
})
3
  • @RobertSösemann Let me know what you did differently compared to the repro posted above. Dec 10, 2017 at 20:21
  • The only obvious difference is that instead of passing an Account[] we pass a Map<Id, CustomObject__c> and populate recordList by initializing it as [] and pushing items from recodsMap[recordId] Dec 11, 2017 at 0:13
  • Ravi, please see my "workaround" answer below. I got help by Kris from the Aura team and there seems to be a bug. He is going to file it and said there is a slight chance there is already a fix delivered in Spring'18. In case you discuss and assess this issue again internally I would love to see an aditional answer or comments by you. Dec 11, 2017 at 8:56
1

An incredibly helpful developer of the Aura Platform team did a Hangout session with me to debug this and confirmed that this is either an open bug or something that will be fixed in Spring'18.

For the time being this way to access the reference works as a workaround

myMethod: function(cmp) {
   ...

   for(var i=0; i<records.length; i++) {
        var record = cmp.get("v.records."+i);
        record['Name'] = "Acme";
        cmp.set("v.records." + i, record);
    }
}

So the important thing is not the loop type or Aura type of the attribute. It's the way how I get the reference by cmp.get("v.records." + i) out of the array and how I set it using cmp.set("v.records." + i, record).

Note: My code was way more complex than the samples I gave here. Instead of and Account array it was a Map. Instead of accessing the Name field it had to work for generic fields and other similar nifty complexities

1
  • great spot, seems like cmp.get() is doing the magic casting refrerences into real records.
    – itsmebasti
    Dec 11, 2017 at 10:20

You must log in to answer this question.

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