Adding Lightning Web Components to Flow Screens

Note! This is a preview of Winter ’20 Functionality

For the last two years, Flow creators have been able to add lightning component to Flow Screens. This has powerfully extended the capability of screens. We’ve had a great time creating reusable components as a community, and many of those components are available for use to all parties.

The underlying web technology enabling these components, known as Aura, has served Salesforce well, but it’s more than 5 years old, and that’s a long time when it comes to changes in web technology. Salesforce recently released a major upgrade to its web technology called Lightning Web Components, and the Flow team has been working on making it possible to embed this new generation of components into Flow screens. With the Winter ’20 release, that will be now be possible, so let’s take a look at how it’s done.

In this demonstration, we create an LWC that allows the user to manually add and manage sharing settings on a record. This allows a Sharing button to be placed on Lightning record pages. The guts of this component consist of code written by resident Salesforce evangelist Shane McLaughlin.

To get an LWC to appear in Flow’s Screen Builder, you add a ‘lightning_FlowScreen’ target to the component’s meta file:

To expose attributes from the LWC, you first use @api in the LWC’s javascript, as is standard for any property you want to make accessible outside of the component:

Then you additionally specify which of these public properties should be additionally made visible in Flow by adding a targetConfig section:

The type field supports the same types that Flow supports with aura components. That includes specific Sobjects like “Contact” and specific SObject collections like “Account[]”.

Note the following syntax for specifying SObjects and Apex Classes:

  • @salesforce/schema/<name of sObject>
  • apex://<name of Apex class>
  • add array notation if it is a collection []

with a namespaced org you have to include the namespace in the type string using the namespace prefix. So, for SObjects it would look like type=”@salesforce/schema/myNs__CoolCustomObject__c” and similarly for apex type=”apex://myNs__MyApexClass”

The ‘role’ parameter can be added to property elements inside of targetConfigs. It has been created specifically to provide support for Flow’s ability to limit access to inputOnly or outputOnly. If no role is specified, the property is available in both directions.

Considerations When Working With Targets

Be Careful about Mixing Your Targets

The support mentioned above for SObjects and Apex Classes works only for Flow and not for other targets like lightning__RecordPage. So something like this will fail:

This can be hard to debug because the each target has its own validation, but the errors reported back don’t specify which target is having a problem. So we’ve seen cases where people think that Flow is complaining when it’s App Builder that’s complaining.

Each property has to meet the requirements of each Target that mentions it.

Example: App Builder (and thus lightning__RecordPage, lightning__AppPage, lightning__HomePage) requires that if you add a ‘required’ attribute you also have to add a ‘default’ attribute. Flow does not have this requirement.

I’m finding the safe thing to do is to separate the App Builder targets from the Flow target, like this:

This doesn’t eliminate all problems. In the example above, Flow is forced to add a default attribute to editTabName because editTabName is also used in the AppBuilder targets, where that required/default issue is enforced. But it makes it easier to debug because you can comment out one set and see which validation is causing problems.

Events and Notification

Unlike Aura, the parent component doesn’t automatically learn when something changes in the child component.

To notify the runtime of changes in the value of an attribute, the component must fire the FlowAttributeChangeEvent event. For example, if you’ve exposed an attribute in your component and want the value of that component to be available downstream in the flow, you need to fire this event whenever the value changes so that the Flow runtime component will have the latest value when the user clicks Next (For architectural reasons, the Flow runtime doesn’t have the ability to read the values of the attributes of its child components).

In the following example, a ToDo LWC dispatches a FlowAttributeChangeEvent when a new item is added to the list of To Do’s:

import { LightningElement, api, track } from 'lwc';
import { FlowAttributeChangeEvent, FlowNavigationNextEvent } from 'lightning/flowSupport';
export default class Todos extends LightningElement {
    @api
    availableActions = [];
    @api
    get todos() {
        return this._todos;
    }
    set todos(todos = []) {
        this._todos = todos;
    }
    @track _todos = [];
    get hasTodos() {
        return this._todos && this._todos.length > 0;
    }
    handleUpdatedText(event) {
        this._text = event.detail.value;
    }
    handleAddTodo() {
        this._todos.push(this._text);
        // notify the flow of the new todo list
        const attributeChangeEvent = new FlowAttributeChangeEvent('todos', this._todos);
        this.dispatchEvent(attributeChangeEvent);
    }
    handleGoNext() {
        // check if NEXT is allowed on this screen
        if (this.availableActions.find(action => action === 'NEXT')) {
            // navigate to the next screen
            const navigateNextEvent = new FlowNavigationNextEvent();
            this.dispatchEvent(navigateNextEvent);
        }
    }
}

See additional pieces of the To Do example

Validation

To link an LWC into Flow’s validation framework, create a function called “validate” in your js file:

@api
validate() {
    if(/* true conditions */) { 
         return { isValid: true }; 
        } 
    else { 
    //If the component is invalid, return the isValid parameter as false and return an error message. 
         return { 
               isValid: false, 
               errorMessage: '/*A message that helps your user enter a valid value or explains what went wrong.*/' 
                }; 
     }}) 
}

Navigation

To programmatically transition a Flow with an LWC component, you dispatch an event using this syntax:

import { LightningElement, api } from 'lwc';
import { FlowNavigationNextEvent } from 'lightning/flowSupport';

export default class NameOfYourComponent extends LightningElement {

   @api
   handleGoNext() {
       const nextNavigationEvent = new FlowNavigationNextEvent();
       this.dispatchEvent(nextNavigationEvent);
   }
}

The supported events are:

  • FlowNavigationNextEvent
  • FlowNavigationBackEvent
  • FlowNavigationFinishEvent
  • FlowNavigationPauseEvent

Sample Gists

https://gist.github.com/alexed1/e3e580103375120ca2dc1374bb4f73ff

10 replies
  1. Chris V
    Chris V says:

    Any chance you can elaborate on the validation portion? Im an Admin, not a Dev but I was able to build a good selection of LWC but still cannot get required to work like I did in my aura components.

    Reply
    • Alex Edelstein
      Alex Edelstein says:

      Are you still having trouble? Note that around the time you posted this, we fixed the example above to show that you need to put @api on the validate method. If that doesn’t solve the problem, feel free to post some code here.

      Reply
      • Chris V
        Chris V says:

        Good news! Adding @api made the validate method work! I have isRequired working now. Next I need to get min and max values working.

        import {
        LightningElement,
        track,
        api
        } from “lwc”;

        export default class Input extends LightningElement {
        @api value = “”;
        @api initials = “”;
        @api label = “”;
        @api compId = “”;
        @api min = “”;
        @api max = “”;
        @api isRequired = false;
        @track length = 0;

        @api
        handleChange(event) {
        this.value = event.target.value;
        this.length = this.value.length;
        }

        @api
        validate() {
        if (this.isRequired === false || this.length >= 1) {
        return {
        isValid: true
        };
        // eslint-disable-next-line no-else-return
        } else {
        return {
        isValid: false,
        errorMessage: ‘Please complete: ‘ + this.label
        };
        }
        }
        }

        Reply
  2. ivelin ganev
    ivelin ganev says:

    Hello guys, i have a question.
    Aura property –
    LWC property –

    Please can you help me, why LWC property always give me “You specified an invalid type for” that response?
    How can i get a collection of the Flow Action “get records” and pass it to the Javascript?

    Reply
    • Alex Edelstein
      Alex Edelstein says:

      What does your targetConfig look like? Note that the lwc syntax is a little unfamiliar. If you’re passing a collection of Contacts, for example, you would put @salesforce/schema/Contact[] in the type attribute of your property.

      Reply
      • ivelin ganev
        ivelin ganev says:

        Thank you for the response.
        My property looks like ( property name=”allContacts” type=”Contact[]” ).
        Now i tried with ( property name=”allContacts” type=”@salesforce/schema/Contact[]” ) but the response was “You specified an invalid type for ‘allContacts'”

        Reply
  3. Ivelin Ganev
    Ivelin Ganev says:

    Hello everyone! Can you tell me can I still use aura:attribute name=”selectedRows” type=”SObject[]” but with the new syntax of LWC? I mean I would receive random sObject and want to handle it.

    Many thanks!

    Reply
  4. Jens Becker
    Jens Becker says:

    Hi Alex,

    Are you aware, if we can use custom metadata type arrays for input properties? I tried

    Which I can deploy, however, the input possibility in the flow disappears. So I assume, this works for APEX defined and “real” sObjects only.
    Am I correct?

    Thanks,

    Jens

    Reply
  5. Gordon Vaynapel
    Gordon Vaynapel says:

    Has anyone tried a @wire(getRecord, { recordId: ‘$recordId’, FIELDS }) with this? I cant seem to get anything back no errors no data.

    Reply

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply