I think that after spending few hours I found the answer for the above. Still not sure if it's a bug or a feature of the framework, but definately it has something to do with how secure objects are constructed.
You need to make sure all possible properties for the object are defined (even if null) when you call component.set("v.obj", obj);
for the first time.
Here is example component & controller, with one simple attribute (initialized on init), and two buttons calling log & set functions of controller:
<aura:component >
<aura:attribute name="testAtr" type="Object" access="global"/>
<aura:handler name="init" value="{!this}" action="{!c.initialize}"/>
<p><a onclick="{!c.logAtr}">Console Log Parent</a></p>
<p><a onclick="{!c.setSth}">Set Something</a></p>
</aura:component>
({
initialize : function(component, event, helper) {
var obj = {};
obj.data = 'test';
// obj.someNewProperty = null;
component.set("v.testAtr", obj);
},
logAtr : function(component, event, helper) {
console.log(component.get("v.testAtr.someNewProperty"));
},
setSth : function(component, event, helper) {
var testAtr = component.get("v.testAtr");
testAtr.someNewProperty = 'New Property';
component.set("v.testAtr", testAtr);
},
})
If the line obj.someNewProperty = null;
in initialize
method is commented out, console.log
returns undefined
even if you call setSth
method that actually sets value of obj.someNewProperty
.
If the line obj.someNewProperty = null;
in initialize
method is present, console.log
returns null before calling setSth
method and 'New Property'
after calling it.
EDIT:
But this has some drawbacks. Let's imagine we have a simple form for sObject. You need to initialize object with all the possible properties. If you don't do this - no values will be kept. If these lines: acc.Name = null; acc.Phone = null; acc.Account_Status__c = null;
are commented as in below example - value put in the input text is not bind to testAcc
attribute.
<aura:component >
<aura:attribute name="testAcc" type="Account" access="global"/>
<aura:handler name="init" value="{!this}" action="{!c.initialize}"/>
<p>Name: <ui:inputText value="{!v.testAcc.Name}"/></p>
<p>Phone: <ui:inputText value="{!v.testAcc.Phone}"/></p>
<p>Status: <ui:inputText value="{!v.testAcc.Account_Status__c}"/></p>
<p><a onclick="{!c.logAcc}">Console Log Acc</a></p>
</aura:component>
({
initialize : function(component, event, helper) {
var acc = {};
// acc.Name = null;
// acc.Phone = null;
// acc.Account_Status__c = null;
component.set("v.testAcc", acc);
},
logAcc : function(component) {
console.log(component.get("v.testAcc"));
}
})
After doing some testing, I can say that this is very inefficient. Loading more than about 6 records caused a separate loading to occur. While the system did not crash, as I hypothesized originally in my comment, the total loading time was incredibly large. For about 800 records, it took over 20 seconds to load, about 40 records per second. And this was for a field specified by the fields attribute. This is opposed to my PagingSortingDemo, which loads about 52,770 records with 4 fields in three seconds, or about 17,500 records per second, a total increase in speed of about 43,900%.
The moral of the story is that you should only use Lightning Data Service to load one record at a time. Similarly, if you're going to choose to save data, make a list and save them all at once. Lightning Data Service is ideal for things like Quick Actions that update or query the related record (and with even better performance when using the cache). It is not ideal for bulk loading or saving of data.
Hopefully, they'll introduce a bulk-friendly option for both loading and saving data in the future. For now, you should prefer to use Apex Code whenever you need to upload more than a few records at a time.
Best Answer
Using the Lightning Data Service - Considerations page as reference, it appears that currently you are able to work with data across spanning relationships (lookup, master-detail) but not collections of records (subquery).