I recently rebuilt AnythingPath using the Lightning Data Service, aka Aura Data Service, or Data Service, or force:recordData fka force:recordPreview.

If you just want to peruse the code without my explanations, here's before and after

Data Service is Awesome

It's more performant, let a lot of apex be removed, and simplifies the architecture.

The original version was implementing this dynamically created streaming topic to get updates when you changed the value using the edit button or the details inline edit...basically hacking to get what force:recordData gives you automatically (shared data state between everything on the page).

And that was never going to work in the community template version (no Streaming API available there). Now the component works just as well in communities.

I've also grown up in how I handle callbacks and errors (see LightningErrorHandler and the extendable promise component that this makes use of). So some of the code looks cleaner, but that's not just force:recordData.

Handy bonus--the error object from force:recordData matches the one from apex calls, so I can just pass that error to my errorHandler unchanged.

Gotcha #1: mode

force:recordData has two modes, VIEW and EDIT. Choosing either has some tradeoffs:

And my component needed both. If someone changes the status via the edit button or inline on the details section, I want the path to update. But if they click on some stage on the path (subject to a design option) I want to edit the record.

Let's have both!

<force:recordData aura:id="frd" recordId="{!v.recordId}" fields="{!v.formattedFields}" mode="VIEW" targetFields="{!v.fields}" recordUpdated="{!c.frdgo}"/>

<force:recordData aura:id="frde" recordId="{!v.recordId}" fields="{!v.formattedFields}" mode="EDIT" targetFields="{!v.fieldsE}"/>

You can read the code, but I bound the visible UI to the frd instance. But the onclick binding updates the field on frde and calls its save method.

But since frd is getting interactions from other components on the page, including frde we're all good.

Don't try this because it doesn't work: component.find("frd").set("v.mode", "EDIT"). It doesn't throw an error on compile or execute, but the component will fuss at you if you try to use a save method, almost like it's no there.

Gotcha #2: Immutable objects

There are 2 places where the record goes: targetRecord and targetFields. You can make an aura:attribute for either or both.

targetFields gives you a simpler structure, name-value pairs.

targetRecord gives you a more thorough object, where fields are just one of the child objects. The also include field labels, formatted values vs. raw, nice handling of lookups, etc.

Unfortunately, there's a known issue where you can't modify nested javascript objects in Lightning Components. This will fail:

let record = component.get("v.targetRecord");
record.fields.fieldName.value = 'some other value';

The value never gets updated. So if you're going to update anything, use targetFields since there's no nesting. You can still use targetRecord if you need that richer version, just don't try to update it in javascript.

Gotcha #3 : order of execution

I used force:recordData like this, where I wanted to make the fields attribute dynamic based on user input.

<force:recordData aura:id="frd" recordId="{!v.recordId}" fields="{!v.formattedFields}" mode="VIEW" targetFields="{!v.fields}" recordUpdated="{!c.frdgo}"/>

That formattedFields attribute only needs one string. So I though I'd set it on init

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

and the controller:

doInit : function(component) {
    let temp = [];
    temp.push(component.get("v.pathField"));
    component.set("v.formattedFields", temp);
}

where pathField is my AppBuilder-defined input.

Because force:recordData is a markup component, it's not waiting on init. And it doesn't handle the error by failing silently or logging to the console--you'll get a giant ugly modal and everything else on the page stops.

So I ended up putting an aura:if around what you saw earlier. They can't be on the page until that attribute gets set.

<aura:if isTrue="{!v.formattedFields.length==1}">
    <force:recordData aura:id="frd" recordId="{!v.recordId}" fields="{!v.formattedFields}" mode="VIEW" targetFields="{!v.fields}" recordUpdated="{!c.frdgo}"/>
    <force:recordData aura:id="frde" recordId="{!v.recordId}" fields="{!v.formattedFields}" mode="EDIT" targetFields="{!v.fieldsE}"/>
</aura:if>

Other odds and ends

The only apex call I had to make was to get the values for a picklist. Looking forward to have a metadata-infused version for recordData so I don't have to write apex for that, either.

Most of the documentaiton in the dev guide is about binding other ui components to the force:recordData. There's less emphasis on dealing with the component via javascript--would love to see more of those examples.

This (what I did) seems to work flawlessly in a Napili community template, even though the docs don't explicitly say that it does. I have a community wrapper around it, to make it possible to modify the recordId and sObjectName attributes.