12

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.

  • 6
    Hey Bob, SF should be paying you to beta test the Locker Service! Kudos for detecting all these issues for the rest of us! – crmprogdev Jun 10 '16 at 12:58
  • I've added a question of my own here: salesforce.stackexchange.com/questions/127047/… With an example of my own - binding to a single property in an object has this issue, and also to properties of objects in arrays. Interestingly the framework is saving the values somewhere but they're getting decoupled from the objects. – Charles T Jun 15 '16 at 2:29
  • Charles as you suspected its the same issue but additional data points are always appreciated. Yes, the underlying cause is the mechanism that LS currently uses to proxy objects is not correctly updating the backing object and the values get dropped. We fixed a similar issue with arrays recently and are working on the object fix. – Doug Chasman Jun 15 '16 at 12:06
  • 2
    Hi Bob, my sandbox has been updated and now this is working better than it used to. A word of advice - all object fields you plan to bind to must be defined the first time you push the object or array to the view, otherwise it may not generate the necessary bindings. For example if you have an object {value='foo', name='bar'} and you bind a checkbox value to {!v.object.selected} then the selected property will have binding problems. If you have {value='foo', name='bar', selected=false} in the first place then binding will work better. – Charles T Jun 22 '16 at 18:08
4

There are secure proxies involved here for certain - currently arrays and raw objects are being sent through filtering/wrapping/unwrapping. We just fixed (deploys in the next patch Tuesday) a similar issue in that logic specific to arrays where the initial value was getting "pinned". This looks very similar but I do not think the changes I have queued up for the array issue will address this nested object problem. I'm opening a bug to track this now.

I'd also like to second crmprogdev's sentiment and I'm certain that could be arranged if you are interested in joining the Salesforce team :-) I can tell you it always looks easier from the outside though.

The security and supportability challenge we are going after is massive and has never been solved afaik by any platform. The amount that just works and is secure (and fast) is substantial and the surface area we are covering is huge - the testing matrix includes dozens of browser versions, hundreds of libraries, etc and all of the wild and wacky JavaScript and BOM/DOM fun that has accreted in the world over decades. It's tricky. We've also rolled out access= enforcement simultaneously which has been the actual issue in many of the cases we've investigated. There have also been the usual helping of bugs in any release where thousands of developers have been working on a product for the release.

  • I'm pretty convinced its not easy - when I hit one of these issues I have a peruse through the aura code on github to see if I can figure out what might be happening, which is only the case when its really simple! I'm totally on board with your sentiment that this is our window to nail all these issues before the critical update is applied, hence I'm only looking for workarounds after I've reproduced with a simple case I can post here. As always, thanks for the quick response and I'll raise a case. – Bob Buzzard Jun 10 '16 at 14:31
  • Thanks Bob - please let support know I've opened bug W-3188756 that they can link to the case and that R&D is already working on it. – Doug Chasman Jun 10 '16 at 14:42
  • Case has been raised - #13991687 – Bob Buzzard Jun 10 '16 at 16:27
  • Fix has been created and is making its way through the sausage factory now - targeted to deploy in the next available patch to Summer'16 which is scheduled for Tuesday/Wednesday – Doug Chasman Jun 16 '16 at 17:49
  • 1
    Hi @BobBuzzard - was it solved for you. Seems I'm experiencing the same problem. Just wanted to know. – Maciek Simm Jul 4 '16 at 14:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.