[SalesForce] Lightning Component – data binding not working for Object[] after I call function

I have component which holds Object[] attribute. On init action I am loading data in it and showing that for user interaction. All works well and update on input field gets bonded properly with original attribute.

I then call controller function on button click and performs some validations on each element of Object[] and display some error message. After that if I am updating/correcting the values in input field then that value is not getting bind to original Object[] elements.

Maincomponent.cmp:

<aura:attribute name="milestones" type="Object[]" />
<div aura:id="saveconfirmation"></div>
<aura:iteration items="{!v.milestones}" var="mile">
    <c:PGP_VariationMilestone milestone="{!mile}" />
</aura:iteration>

<ui:button label="Save" press="{!c.saveVariation}" class="slds-button slds-button--brand" aura:id="savemilestonebutton" /> 

Here Object[] is list of wrapper class.

PGP_VariationMilestone.cmp

<aura:component controller="PGPVariationCompClass">
    <aura:attribute name="milestone" type="Object" />

    <div class="slds-form-element" style="width:180px;">
        <ui:inputText label="Milestone Number" value="{!v.milestone.name}" disabled="{!(v.milestone.recordId != '') ? 'true' : 'false'}" class="slds-input" labelClass="slds-form-element__label" change="{!c.updateActionOnChange}" />
    </div>

    <div class="slds-form-element" style="white-space: nowrap;width:130px">
        <ui:inputDate aura:id="dueDate" label="Due Date" value="{!v.milestone.dueDate}" displayDatePicker="true" class="slds-input" labelClass="slds-form-element__label" blur="{!c.dateOnChange}" />
    </div>
</aura:component>

MainComponentController.js

saveVariation: function(component, event, helper) {
    var milestoneArray = component.get('v.milestones') ;
    console.log('------in saveVariation milestone-----' + JSON.stringify(component.get('v.milestones')));

    var isValid;
    var milestonenames = '';
    var deliberablenames = '';
    var i, j;
    // deliverables is a list of wrapper class. each milestoneArray element has list of  deliverables[]

    for (i = 0; i < milestoneArray.length; i++) {
        if (!milestoneArray[i].description ||
            !milestoneArray[i].dueDate ||
            (!milestoneArray[i].mpifund && milestoneArray[i].miletype == 'Payment')) {
            isValid = false;
            milestonenames = milestonenames + milestoneArray[i].name + ', ';
        }

        for (j = 0; j < milestoneArray[i].deliverables.length; j++) {
            console.log('------for loop deliverable data----' + JSON.stringify(milestoneArray[i].deliverables[j]));
            if (!milestoneArray[i].deliverables[j].dueDate ||
                !milestoneArray[i].deliverables[j].startDate ||
                !milestoneArray[i].deliverables[j].name ||
                !milestoneArray[i].deliverables[j].description) {
                isValid = false;
                deliberablenames = deliberablenames + milestoneArray[i].deliverables[j].name + ', ';
            }
        }
    }

    console.log('----isValid----' + isValid);

    if (isValid == true) {
        helper.saveVariationHelper(component);
    } else {
        console.log('------in else of isValid error-----');
        $A.createComponents([
                ["ui:message", {
                    "title": "Error",
                    "severity": "error",
                    "closable": false
                }],
                ["ui:outputText", {
                    "value": 'Please correct the errors for Miletones:- ' + milestonenames + 'and Deliverables:- ' + deliberablenames,
                }]
            ],
            function(components, status) {
                if (status === "SUCCESS") {
                    var message = components[0];
                    var outputText = components[1];
                    // set the body of the ui:message to be the ui:outputText
                    message.set("v.body", outputText);
                    var saveconfirmation = component.find("saveconfirmation");
                    // Replace div body with the dynamic component
                    saveconfirmation.set("v.body", message);
                }
            }
        );
    }
}

No error in console. Removing ui:message also didnt worked.

Hope I am clear in explaining the issue. I looked this post also but not sure when fix will be available.

Binding to object values and arrays of objects in Lightning Locker

Best Answer

Something does not look right in your data binding. You declare an aura:iteration through v.milestones and you declare var="mile". But all your bindings inside the iteration are pointing to v.milestone.propertyName when they should be pointing to mile (no v. because this variable is internal to the iteration).

So first off I would try correcting that:

<aura:attribute name="milestones" type="Object[]" />
<div aura:id="saveconfirmation"></div>
<aura:iteration items="{!v.milestones}" var="mile" indexVar="index">
    <div class="slds-form-element" style="width:180px;">
        <ui:inputText label="Milestone Number" value="{!mile.name}" disabled="{!(mile.recordId != '') ? 'true' : 'false'}" class="slds-input" labelClass="slds-form-element__label" change="{!c.updateActionOnChange}"/>
    </div>
    <div class="slds-form-element" style="white-space: nowrap;width:130px">
        <ui:inputDate aura:id="dueDate" label="Due Date" value="{!mile.dueDate}" displayDatePicker="true" class="slds-input" labelClass="slds-form-element__label" blur="{!c.dateOnChange}"/>
    </div>
</aura:iteration>

<ui:button label="Save" press="{!c.saveVariationHelper}" class="slds-button slds-button--brand" aura:id="savemilestonebutton" />

Next, in your JavaScript, you declare var milestones = component.get('v.milestones'); but then you repeatedly make reference to milestoneArray which doesn't exist. I am surprised you are not seeing any console errors!

In my sandbox the fix to object binding in Lightning Locker has already rolled out so if you don't have the fix yet, you will soon. I should advise that any fields that do not already exist in the array's objects may face some binding difficulties. Sometimes I also find that if data objects were passed into the component as a parameter, Locker Service will proxy them before your code ever gets to see them and this can screw up the bindings. So in one of my components I basically had to clone the passed-in object, add necessary fields, and then assign it to a new private attribute that is only used internally.

If you made some errors in the code you posted here, please edit your original post and comment to let me know.