[SalesForce] Lightning Web Component – Navigation – Show Link in a List / Table to Open Record

I need to create a link to an object within a data-table listing.
For example, list of contacts on a data table, when the user clicks the "name", it opens that Contact record view.
This seems simple enough, and yet, I'm having trouble finding a solution or example anywhere out on the internet or trailhead. It seems like all the solutions I find are using "tiles" and cascading events. I don't need anything that complicated.

Let's take for example: https://sfdccoder.wordpress.com/2019/02/21/lightning-web-component-load-contacts-list-example/

<template>
<lightning-card title="Contact ListView" icon-name="custom:custom67">

    <template if:true={listView.data}>
        <div class="slds-m-around_medium">
        <template for:each={contacts} for:item="contact">
            <p key={contact.Id}>{contact.fields.Name.value} – {contact.fields.Phone.value} </p>
        </template>
        </div>
    </template>

    <template if:true={listView.error}>
        Error in loading the data ..
    </template>

</lightning-card>

In this example, they are not making the name a link — I would like to make it a link that would open that contact's record view.

Assuming I were to add the link using something like this:

<p key={contact.Id}>
<lightning-button variant="base" label={contact.fields.Name.value} title="View Contact" onclick={viewContact}></lightning-button> 
– {contact.fields.Phone.value}

What would the "viewContact" js look like?

Something along the lines of:

viewContact() {
    this[NavigationMixin.Navigate]({
        type: 'standard__recordPage',
        attributes: {
            recordId: ?????,
            objectApiName: 'Contact',
            actionName: 'view'
        }
    });
}

I don't understand how to pass the contact.Id value from the HTML to the JS (?????)
Or, using:

<lightning-formatted-url value={?????} tooltip="View Contact" label={contact.fields.Name.value} target="_blank"></lightning-formatted-url>

(I realized the button isn't what I wanted)

In this example, in the past I would have just put "/{contact.Id}" — but that doesn't work with the lightning web component : it gives me the error: Ambiguous attribute value value="/{c.Id}". If you want to make it a string you should escape it value="/{c.Id}" — and if I escape it using /, then the link it forms is incorrect.

UPDATE: Per answer below, can pass ID using "value" and/or "data-*"
My full updated code is below for future reference:

sesResultsContact.js

import { NavigationMixin, CurrentPageReference } from 'lightning/navigation';
import { LightningElement, wire } from 'lwc';

import getContactList from '@salesforce/apex/sesController.getContactList';

export default class SesResultsContact extends NavigationMixin(LightningElement) {
@wire(CurrentPageReference) pageRef;
@wire(getContactList) contacts;

viewContact(event) {
    this[NavigationMixin.Navigate]({
        type: 'standard__recordPage',
        attributes: {
            recordId: event.target.value,
            objectApiName: 'Contact',
            actionName: 'view'
        }
    });
}

viewContact2(event) {
    this[NavigationMixin.Navigate]({
        type: 'standard__recordPage',
        attributes: {
            recordId: event.target.dataset.id,
            objectApiName: 'Contact',
            actionName: 'view'
        }
    });
}

}

sesResultsContact.html

<template>
<lightning-card title="Contact" icon-name="standard:contact">
    <div class="slds-card__body_inner">

        <template if:true={contacts.data}>
            <table class="slds-table slds-table_fixed-layout slds-table_bordered">
                <thead>
                    <tr class="slds-line-height_reset">
                        <th class="" scope="col">
                            <div class="slds-truncate" title="Name">Name</div>
                        </th>
                        <th class="" scope="col">
                            <div class="slds-truncate" title="Email">Email</div>
                        </th>
                        <th class="" scope="col">
                            <div class="slds-truncate" title="Phone">Phone</div>
                        </th>
                        <th class="" scope="col">
                            <div class="slds-truncate" title="Account">Account</div>
                        </th>
                    </tr>
                </thead>
                <tbody>
                    <template for:each={contacts.data} for:item="c">
                        <tr class="slds-hint-parent" key={c.Id}>
                            <th data-label="Name" scope="row" class="slds-truncate">
                                <lightning-button-icon icon-name="utility:search" variant="bare"
                                    alternative-text="Open record" onclick={viewContact} value={c.Id}>
                                </lightning-button-icon>
                                <a onclick={viewContact2} title="View Contact 2" data-id={c.Id}
                                    target="_blank">{c.Name}</a>
                            </th>
                            <td data-label="Email">
                                <div class="slds-truncate" title={c.Email}>
                                    <lightning-formatted-email value={c.Email}></lightning-formatted-email>
                                </div>
                            </td>
                            <td data-label="Phone">
                                <div class="slds-truncate" title={c.Phone}>
                                    <lightning-formatted-phone value={c.Phone}></lightning-formatted-phone>
                                </div>
                            </td>
                            <td data-label="Account">
                                <div class="slds-truncate" title={c.Account.Name}>
                                    <lightning-formatted-url value={c.AccountId} label={c.Account.Name}
                                        target="_blank"></lightning-formatted-url>
                                </div>
                            </td>
                        </tr>
                    </template>
                </tbody>
            </table>

        </template>

        <template if:true={contacts.error}>
            <c-error-panel errors={contacts.error}></c-error-panel>
        </template>

        <div class="slds-align_absolute-center">
            <lightning-button label="Show More" variant="base" disabled> </lightning-button>
        </div>

    </div>
</lightning-card>

sesController.cls

public with sharing class sesController {

@AuraEnabled(cacheable=true)
public static List<Contact> getContactList() {
    return [ SELECT Id, Name, Phone, Email, AccountId, Account.Name 
             FROM Contact 
             WHERE Email != null 
             ORDER BY Name
             LIMIT 1];
}

}

Best Answer

Although at first I fought the idea of creating a component "just" to display a link... I came up with this solution which will generate a navigation link using parameters passed to the component. It even creates the HREF URL so the user can right-click and open the link in a new window or copy the link. It's generic so it can be used to generate any navigation link, and seems pretty simple and elegant.

navigationLink.js

// *********************************************************************
// ** NavigationLink
// *********************************************************************
// ** Written by: Paul J. Narsavage <NarsavageP@dnb.com>
// ** Date: Mar 28, 2019
// ** Purpose: Generates a navigation link using parameters passed to the component
// ** Reference: https://developer.salesforce.com/docs/component-library/documentation/lwc/use_navigate
// **********************************************************************
//  1/25/22 PJN - Adjust to use "renderedCallback" instead of "connectedCallback" in order to keep link updated if changed
//  1/26/22 PJN - Prevent issues if no record-id is passed

/* Component Parameters:

    <c-navigation-link
        label="The Label"
        title="The Title"
        type="PageReferenceType"
        record-id="RecordId"
        api-name="ApiName"
        object-api-name="ObjectApiName"
        relationship-api-name="RelationshipApiName"
        page-name="PageName"
        action-name="ActionName"></c-record-link>
*/

import { LightningElement, api, track } from 'lwc';
import { NavigationMixin } from 'lightning/navigation';

export default class NavigationLink extends NavigationMixin(LightningElement) {
    @api label; // Text to be displayed as the link
    @api title; // Text to be displayed when hovering on the link (optional, will default to label)

    @api type;               // PageReference Type (default of "standard__recordPage" if recordId provided)
    @api recordId;           // Id of the record
    @api pageName;           // The name of the Page
    @api apiName;            // API Name of Page
    @api objectApiName;      // Object type
    @api relationshipApiName // The API Name of Relationship to open
    @api actionName;         // Action to perform when clicked (default of "view" if recordId provided)

    @api replace; // Indicates how the link that is created will affect browser
    //  no [default, normal, open in current window]
    //  yes [replaces current browser history entry]
    //  new [opens in a new window; url only]
    get urlOnly() { return (this.replace === "new"); }

    navigationLinkRef = null;
    @track url;

    renderedCallback() {

        // Set defaults...
        if (!this.title && this.label) this.title = this.label;
        if (this.recordId) {
            if (!this.type) this.type = "standard__recordPage";
            if (!this.actionName) this.actionName = 'view';
        }
        if (!this.replace) this.replace = 'no';

        if (!this.type) return;

        // Generate the page reference for NavigationMixin...
        this.navigationLinkRef = {
            type: this.type,
            attributes: {
                recordId: this.recordId,
                pageName: this.pageName,
                apiName: this.opiName,
                objectApiName: this.objectApiName,
                relationshipApiName: this.relationshipApiName,
                actionName: this.actionName
            }
        };

        // Set the link's HREF value so the user can click "open in new tab" or copy the link...
        if (this.navigationLinkRef) this[NavigationMixin.GenerateUrl](this.navigationLinkRef)
            .then((url) => { this.url = url });

    }//renderedCallback

    handleClick(event) {
        if (!this.navigationLinkRef) return;

        var doReplace = (this.replace === "yes");

        // Stop the event's default behavior (don't follow the HREF link) and prevent click bubbling up in the DOM...
        event.preventDefault();
        event.stopPropagation();

        // Navigate as requested...
        this[NavigationMixin.Navigate](this.navigationLinkRef, doReplace);

    }//handleClick

}

navigationLink.html

<template>
        <a if:true={urlOnly} href={url} target="_blank" title={title}>{label}</a>
        <a if:false={urlOnly} href={url} onclick={handleClick} title={title}>{label}</a>
</template>

Examples for usage:

<c-navigation-link label="Chatter" title="Navigate to a standard page. In this example, Chatter home."                 
    type="standard__namedPage" page-name="chatter"></c-navigation-link>
<c-navigation-link label="Home" title="Navigate to a standard page. In this example, the Home page."                
    type="standard__namedPage" page-name="home"></c-navigation-link>
<c-navigation-link label="Hello" title="Navigate to a custom page. In this example, the Hello tab."                  
    type="standard__navItemPage" api-name="Hello"></c-navigation-link>
<c-navigation-link label="Files Home" title="Navigate to an object home page. In this example, the Files home."           
    type="standard__objectPage" action-name="home" object-api-name="ContentDocument"></c-navigation-link>
<c-navigation-link label="List View" title="Navigate to a list view. In this example, Contacts list."                    
    type="standard__objectPage" object-api-name="Contact" action-name="list"></c-navigation-link>
<c-navigation-link label="New Contact" title="Navigate to a new record page. In this example, a new contact page."         
    type="standard__objectPage" object-api-name="Contact" action-name="new"></c-navigation-link>
<c-navigation-link label="View Contact" title="Navigate to a record page. In this example, a contact."                      
    type="standard__recordPage" record-id="003a000002UkPDtAAN"></c-navigation-link>
<c-navigation-link label="Edit Contact" title="Navigate to a edit record page. In this example, a contact."                 
    type="standard__recordPage" record-id="003a000002UkPDtAAN" action-name="edit"></c-navigation-link>
<c-navigation-link label="Account Contacts" title="Navigate to a related list. In this example, an Account's Related Contacts." 
    type="standard__recordRelationshipPage" record-id="001a000001s8zS1AAI" object-api-name="Account" relationship-api-name="Contacts" action-name="view"></c-navigation-link>
Related Topic