Friday, 11 May 2018

Lightning Component Navigation in Summer 18

Lightning Component Navigation in Summer 18

Introduction

In my last but one post (Toast Message from a Visualforce Page) I explained how I needed to solve the problem due to the force:createRecord event not respecting overrides, instead always opening the standard modal-style form and there was no GA mechanism to navigate to a component, force:navigateToComponent being still in beta.

A couple of days after, the Summer 18 release notes came out in preview and towards the end I saw that there was finally a solution for this.

lightning:isUrlAddressable Interface

The new lightning:isUrlAddressable interface allows me to expose a Lightning Component via a dedicated URL. I simply implement this on my component and my component is available at the (current) URL :

   https://<instance>.lightning.force.com/lightning//cmp/<namespace>__<component name>

However, one thing we can be certain of with Lightning Components is change, so rather than hardcoding the URL to my component, there’s a way to get the platform to dynamically generate it.

lightning:navigation Component

The new lightning:navigation component is a headless (i.e. does not have a user interface aspect) service component that exposes methods to help with navigation. The method that I’m interested in is navigate(PageReference). Using this I can create a PageReference JavaScript object that describes the location I want to navigate to and leave it up to the lightning:navigation component to figure out the appropriate URL to hit. 

The beauty of using this mechanism is that my components are decoupled from the URL format. If the Lightning Components team decide the change this, it has no impact on me - the Lightning Navigation component gets updated and everything still works as it always did.

Example

Note - as these features are part of Summer 18, at the time of writing they aren’t available outside of pre-release orgs, which was where I created my example.

I’ve created a component (WebinarOverride)that implements lightning:actionOverride, so that I can use it to override the default new action, just to show that it has no effect, and lightning:isUrlAddressable so that I can navigate to it.

<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes,force:hasRecordId,lightning:actionOverride,lightning:isUrlAddressable">
	<lightning:card iconName="standard:event" title="Override">
	    <lightning:formattedText class="slds-p-left_small" 
value="This is the webinar override component."/> </lightning:card> </aura:component>

Note that it doesn’t do anything in terms of functionality, it’s just intended to obviously identify that we’ve ended up at this component.

I then have a component that can navigate to the override called, creatively, NavigationDemo:

<aura:component implements="force:appHostable,flexipage:availableForAllPageTypes">
    <lightning:navigation aura:id="navService"/>
    <lightning:button label="Force Navigate" onclick="{!c.forceNavigate}" />
    <lightning:button label="Lightning Navigate" onclick="{!c.lightningNavigate}" />
</aura:component>

That this has two buttons - one that uses force:createRecord:

var createRecordEvent = $A.get("e.force:createRecord");
createRecordEvent.setParams({
    "entityApiName": "Webinar__c"
});
createRecordEvent.fire();

and one that uses the lightning:navigation component:

var navService = cmp.find("navService");
var pageReference = {
    "type": "standard__component",
    "attributes": {
        "componentName": "c__WebinarOverride" 
    }, 
    "state": {}
};

navService.navigate(pageReference); 

Note that as the navigation aspect is provided by a headless service component, I have to locate the components via cmp.find() and then execute the method on that. Note also my pageReference object, which has a type of standard__component (even though this is a custom component - go figure) and supplies the component name as an attribute.

I’ve overridden the Lightning Experience actions for the Webinar custom object with my WebinarOverride component:

Screen Shot 2018 05 06 at 10 14 56

So clicking the New button on the webinar home page takes me to the override. If I now open my NavigationDemo component:

Screen Shot 2018 05 06 at 10 18 27

clicking the Force Navigate button is disappointing as always:

Screen Shot 2018 05 06 at 10 18 37

however, clicking on the Lightning Navigate button takes me to my WebinarOverride component:

Screen Shot 2018 05 06 at 10 18 55

You can find the full component code at the Github repository.

Conclusion

The lightning:navigate component also retires the force:navigateToComponent() function, although it’s not clear if there was a WWE-style ladder match to determine who retired. This function has had a troubled existence, leaking out before it was ready and then being withdrawn, causing wailing and gnashing of teeth in the developer community, then entering a short beta before being deprecated. Farewell little buddy, you gave it your best shot but couldn’t cut it.

The headless component mechanism feels like the way that new functionality like this will be added, rather adding functions/events etc to the framework. I guess this keeps the framework as lightweight as it can be and doesn’t force functions on applications that don’t care about them.

One additional point to note - if you forget the lightning:isUrlAddressable interface on your target component the lightning:navigation function will still happily return the URL, but when you navigate to it you will get a not particularly helpful error message that the page isn’t supported in the Lightning Experience.

Finally, I’d still prefer force:createRecord to respect overrides. While I can make my new object component configurable via a custom setting or custom metadata type, from a low code perspective I don’t want to have to change two places to use a different one. Still, we’re in a better place that we have been for a couple of years.

Related Posts

 

 

10 comments:

  1. How to pass and set the values for the attributes in the child component using lightning:navigation, Please help me

    ReplyDelete
  2. Hi Bob, Thanks for the the approach will this work in communities as well?

    ReplyDelete
  3. Hi Bob, thanks for the approach. will this work in communities aswell?

    ReplyDelete
  4. Great post.
    I can't get the recordId passed to the component.
    I tried a couple of parameter possibilities:
    var pageReference = {
    "type": "standard__component",
    "attributes": {
    "componentName": "c__AssetsList",
    "recordId" : "accntRecordId"
    },
    "componentAttributes": {recordId : accntRecordId},
    "state": {}
    };

    Can you help?

    ReplyDelete
  5. Hi Bob, have you had any luck with using the lightning:navigate component on mobile salesforce? Have been trying this out but ran into issue with the hamburger menu not correctly keeping the history and allowing the users to go back. see https://salesforce.stackexchange.com/questions/225594/lightningnavigation-salesforce-mobile-ios-android-back-button Thanks Bob! Great article as always!!

    ReplyDelete
  6. Good article Bob! Always a fan your work!! BTW, can this lightning:navigate be used to navigate to sub-tabs on record detail pages? Let's say, I have a sub-tab next to 'Stage' tab in Opportunity record detail page and I want to navigate to the sub-tab on click of a action on the opportunity record detail page, can we use this?

    ReplyDelete
  7. Thanks for this! I was looking for a basic walkthrough of how to use this new navigation element and this answered my questions.

    ReplyDelete
  8. Hi Bob, thanks for your helpful articles. One question - my understanding is lightning:navigation does not support Creating a new record. For that we still need to do e.force.createRecord . Is that right?

    ReplyDelete
  9. Hi when I use navigate() to go from cmp1 to cmp 2, all state attributes from cmp1 can be accessed fine in cmp2.

    c__cmp2?c__params=%5Bobject%20Object%5D

    Then I hit refresh on page for cmp2, the state attributes of basic data types such as string can be accessed fine in init(), but for a list, or map type, it can not be read. It is read as the literal "Object".

    Do you know if it is possible to deserialize the object on page refresh too?

    ReplyDelete