Thanks Bob.
I can confirm the issue you are reporting: change handlers are not fired up the chain when members of attribute type "object" are modified. Here is a workaround:
Change:
component.set('v.acc.Name', 'Name from 3!');
To:
var acc = component.get('v.acc');
acc.Name = 'Name from 3!';
component.set('v.acc', acc);
And everything will work.
Here is why you are seeing this behavior, and why the workaround is working:
The rule to remember: all changes are propagated down, but only propagated up when they affect the root of the attribute.
About the workaround: because component.get('v.acc')
is returning a pointer to the object (this is JavaScript after all), we are not not copying the data, and it is not less efficient than calling component.get('v.acc.Name')
. Sure it is more verbose, but not less efficient!
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"));
}
})
Best Answer
Figured out better way to pass data rather than this complex logic. Hence, closing it.
NOTE: I used a list of list custom settings records , stringified them, de-serialised them in apex and then used them. Related question to this was:
How to pass LIST custom setting list(array) from client side to server side controller in lightning?