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"));
}
})
The solution is to declare an event instead of an attribute when you want to provide a callback. Below is myButton.cmp rewritten to use events.
pressEvent.evt
<aura:event type="COMPONENT" />
myButton.cmp
<aura:component>
<aura:attribute name="label" required="true" type="String" />
<aura:attribute name="class" required="false" type="String" />
<!-- Declare an "instance" of pressEvent -->
<aura:registerEvent name="press" type="c:pressEvent"/>
<input type="button" value="{!v.label}" class="{!v.class}" onclick="{!c.onClick}" />
</aura:component>
myButtonController.js
({
onClick : function(component, event, helper) {
helper.trackButtonClick(component, event);
var event = component.getEvent('press');
event.fire();
}
})
This wasn't clear from the documentation, but there are two ways to handle an event. The first is to use the <aura:handler />
markup. To handle the press event of myButton, you could do this:
<aura:handler name="press" event="c:pressEvent" action="{!c.onOk}"/>
I would avoid this approach because if you have more than one button, the press event for all of them would be handled by the same action c.onOk
.
The second way to handle an event is to pass a callback as an attribute in the <c:myButton />
markup. I didn't see this in the Salesforce docs but found it in an unrelated question. This is exactly what I needed. Each instance of myButton gets a unique callback.
myButtonConsumer.cmp
<aura:component>
<!-- No aura:handler necessary. Just pass the callback as an attribute -->
<!--<aura:handler name="press" event="c:pressEvent" action="{!c.onOk}"/>-->
<c:myButton label="OK" class="slds-button" press="{!c.onOk}" />
<c:myButton label="Cancel" class="slds-button" press="{!c.onCancel}" />
</aura:component>
myButtonConsumerController.js
({
onOk: function(component, event) {
alert('OK pressed!');
},
onCancel: function(component, event) {
alert('Cancel pressed!');
}
})
Best Answer
I can say few reason:-
Reference:- Lightning Components Performance Best Practices