I'm seeing some unexpected behaviour with the locker service enabled when updating two fields on an object, one of which is nested inside a sub-object.
I have a lightning application that initialises a JavaScript object and passes references to fields on the object, and the object itself, as attributes to a contained component:
App Markup
<aura:application >
<aura:attribute name="obj" type="object" />
<aura:handler name="init" value="{!this}" action="{!c.init}" />
Top level value: <ui:outputText value="{!v.obj.value1}" /> <br/>
Nested value: <ui:outputText value="{!v.obj.sub.value2}" /> <br/>
<c:MultiFieldTest obj="{!v.obj}" value1="{!v.obj.value1}" value2="{!v.obj.sub.value2}" />
<br/>
<button onclick="{!c.showObj}">Show Object</button>
</aura:application>
app controller
({
init : function(component, event, helper) {
helper.init(component);
},
showObj : function(component, event, helper) {
helper.showObj(component);
}
})
app helper:
({
init : function(cmp) {
var obj={value1:'', sub:{value2:''}};
cmp.set('v.obj', obj);
},
showObj : function(cmp) {
alert('Object = ' + JSON.stringify(cmp.get('v.obj')));
}
})
So the concept is there is an object with the structure:
{value1:'',
sub:{
value2:''}
}
and I want to pass value1 and sub.value2 as attributes to a component that will update them. However, to ensure that the changes are propagated up and down the food chain (as per : Lightning Components - SObject attribute change event only sent to descendants) I am also passing in the object itself. Essentially I want the contained component to update a couple of fields and propagate the changes without having to know which fields it is updating.
The containing component markup:
<aura:component>
<aura:attribute name="value1" type="String" />
<aura:attribute name="value2" type="String" />
<aura:attribute name="obj" type="object"/>
<button onclick="{!c.setValues}">Set Values</button>
</aura:component>
controller:
({
setValues : function(component, event, helper) {
helper.setValues(component);
}
})
helper:
({
setValues : function(cmp) {
var obj=cmp.get('v.obj');
console.log('Obj before = ' + JSON.stringify(obj));
cmp.set('v.value1', 'Value 1');
cmp.set('v.value2', 'Value 2');
obj=cmp.get('v.obj');
console.log('Obj after = ' + JSON.stringify(obj));
cmp.set('v.obj', obj);
}
})
So when the user clicks the button, strings are set into the value1/value2 attributes and the underlying object 'reset' into the page. I then click on the 'Show Object' button, and the results are as expected with the locker service deactivated.
However, when I activate the locker service, only the second update 'sticks' and obj.value1 is empty, although both ui:outputText elements show the value1/value2 details as expected. What appears to be happening is that setting the first attribute updates the page as expected, and the object, but setting the second attribute updates the page and an earlier version of the object, minus the value1 details.
However, if I don't get the obj attribute at the beginning of the setValues method, it works:
setValues : function(cmp) {
// var obj=cmp.get('v.obj'); <---- removed this
cmp.set('v.value1', 'Value 1');
cmp.set('v.value2', 'Value 2');
var obj=cmp.get('v.obj');
console.log('Obj after = ' + JSON.stringify(obj));
cmp.set('v.obj', obj);
}
So pretty easy to work around, but I'm curious as to why this would be necessary with the locker service enabled - there's no secure versions of anything in play and the attributes are all accessible to the component. Its almost like the act of looking at the object fixes a version somewhere, and all updates retrieve that version before applying their update, rather than retrieving the latest version.
{value='foo', name='bar'}
and you bind a checkbox value to{!v.object.selected}
then theselected
property will have binding problems. If you have{value='foo', name='bar', selected=false}
in the first place then binding will work better.