The documentation on this topic emphasizes that any child component should treat the value passed from parent as read-only. If you try to mutate the data passed from parent to child, it results in an error (emphasis mine).
NOTE The child component must treat any property values passed from the owner component as read-only. If the child component tries to change a value passed from an owner component, you see an error in the browser console.
As for your question:
I believe I could solve this with a setter on the child list which would make a copy of that array, but I find that ugly. So my question is how to treat object references when propagated like this?
That's in fact the recommended way to pass data from parent to child is:
To communicate down the containment hierarchy, an owner can set a property on a child component. When you add a component in markup, you can initialize public property values in the component based on property values of the owner component. The data binding for property values is one-way.
So its very likely that what you are observing is true. But, if you really don't want to change data in child, then you should not be attempting to pass the data as reference in first place. As a result, your only option to address the issue is to make sure that you pass a copy by setting a property or calling a function on the child component.
here is a solution that works perfectly. I display a list of opportunity and add a hyperlink on the column name of the data table.
To do that, you have to create a wrapper class in you apex controller like this:
public with sharing class GetAllOpportunities {
@AuraEnabled(cacheable=true)
public static List<DataTableWrapper> getAllOpps() {
List<Opportunity> listOpp = [SELECT Id, Name ,StageName, CloseDate
FROM Opportunity Order By Name asc];
List<DataTableWrapper> response = new List<DataTableWrapper>();
for(Opportunity opp : listOpp){
DataTableWrapper obj = new DataTableWrapper();
obj.oppId = opp.Id;
obj.name = opp.Name;
obj.nameUrl = '/'+opp.Id;
obj.stageName = opp.StageName;
obj.closeDate = opp.CloseDate;
response.add(obj);
}
return response;
}
private class DataTableWrapper {
@AuraEnabled
public Id oppId {get;set;}
@AuraEnabled
public String name {get;set;}
@AuraEnabled
public String nameUrl {get;set;}
@AuraEnabled
public String stageName {get;set;}
@AuraEnabled
public Date closeDate {get;set;}
}
}
note the nameUrl attribute, I'll use it in the js file.
here is the html file:
<template>
<lightning-card title="Opportinity List">
<lightning-datatable data={opportunities} columns={columns} key-field="Id"></lightning-datatable>
</lightning-card>
</template>
And Finally, the js file:
import { LightningElement ,wire,track} from 'lwc';
import getAllOpps from '@salesforce/apex/GetAllOpportunities.getAllOpps';
export default class OpportunityList extends LightningElement {
@track columns = [
{
label: 'Opportunity name',
fieldName: 'nameUrl',
type: 'url',
typeAttributes: {label: { fieldName: 'name' },
target: '_blank'},
sortable: true
},
{
label: 'Stage Name',
fieldName: 'stageName',
type: 'text',
sortable: true
},
{
label: 'Close date',
fieldName: 'closeDate',
type: 'date',
sortable: true
}
];
@track error;
@track opportunities = [];
@wire(getAllOpps)
wiredOpps({error,data}) {
if (data) {
this.opportunities = data;
this.error = undefined;
} else if (error) {
this.error = error;
this.opportunities = undefined;
}
}
}
==========================================================================
Update
=========================================================================
This code below does the same thing without creating any wrapper class. We can only use JS.
import { LightningElement ,wire,track} from 'lwc';
import getAllOpps from '@salesforce/apex/GetAllOpportunities.getAllOpps';
export default class OpportunityList extends LightningElement {
@track columns = [
{
label: 'Opportunity name',
fieldName: 'nameUrl',
type: 'url',
typeAttributes: {label: { fieldName: 'Name' },
target: '_blank'},
sortable: true
},
{
label: 'Stage Name',
fieldName: 'StageName',
type: 'text',
sortable: true
},
{
label: 'Close date',
fieldName: 'CloseDate',
type: 'date',
sortable: true
}
];
@track error;
@track opportunities = [];
@wire(getAllOpps)
wiredOpps(result) {
const { data, error } = result;
if(data) {
let nameUrl;
this.opportunities = data.map(row => {
nameUrl = `/${row.Id}`;
return {...row , nameUrl}
})
this.error = null;
}
if(error) {
this.error = error;
this.opportunities = [];
}
}
}
Apex class
public with sharing class GetAllOpportunities {
@AuraEnabled(cacheable=true)
public static List<Opportunity> getAllOpps() {
return [SELECT Id, Name ,StageName, CloseDate FROM Opportunity Order By Name asc];
}
}
Best Answer
The lightning:datatable assumes that the field name is not an object, but you can have a period in a field name.
Consider this data:
If you had data like this, the output in lightning:datatable would be "Hello", not "World".
Here's a copy-paste example for you:
This is true for both the Aura and Web component versions.
So, to answer your question, yes, you will need to do some post-processing on your data. It can be as simple as: