[SalesForce] Record passed from force:recordData to Apex controller method is null

I am using Lightning Data Services to have the current record already prefetched

<aura:component controller="MyCtrl" implements="force:hasRecordId">
    <aura:attribute name="parent" type="SObject" />

    <aura:handler name="init" value="{!this}" action="{!c.init}" />

    <force:recordData aura:id="recordLoader"
                      recordId="{!v.recordId}"
                      fields="Name,..."
                      targetRecord="{!v.parent}"
                      layoutType="FULL" />

   {!v.market.fields.Name.value} <-- WORKS!
</aura:component>

I can directly display its fields in the component without any problems. But when I pass the record

({
    init: function(cmp, evt, helper) {
        var action = cmp.get("c.queryData");
        action.setParams({ parent: cmp.get("v.parent") });
        action.setCallback(this, function(response) {
            ...
        });

        $A.enqueueAction(action);   
    },
})

into my Apex init method fields are not accessible anymore

public class MyCtrl {

    @AuraEnabled
    public static String queryData(Parent__c parent) {
         // parent is null
    }
}

Best Answer

force:recordData loads the data asynchronously. This means it won't be available in the init method. Instead, listen for a change event and then get your data:

<aura:handler name="change" value="{!v.parent}" action="{!c.loadChildData}" />

loadChildData: function(component, event, helper) {
    var action = component.get("c.queryData");
    action.setParams({ parent: component.get("v.parent") });
    action.setCallback(...);
    $A.enqueueAction(action);
}

The layout for the force:recordData object doesn't align well with the default layout, and the Locker Service Proxy tends to muck things up. Here's an example I finally got working in my org:

change: function(component, event, helper) {
    var action = component.get("c.queryData"), account = component.get("v.account"), param;
    param = { sobjectType: 'Account', Id: account.fields.Id.value, Name: account.fields.Name.value };
    action.setParams({ record: param });
    console.log(param);
    action.setCallback(this, function(result) { console.log(result.getReturnValue()); });
    $A.enqueueAction(action);
}