Mastering in Lightning component attributes

Introduction

          In this post, I am going to explain lightning component attributes. Component attributes are like member variables on a class in Apex.  Lightning component attributes are used to exchange data between components.  They have typed fields that are set on a specific instance of a component and can be referenced from within the component’s markup using an expression syntax. .Use the <aura:attribute> tag to add an attribute to the component or app. All attributes have a name and a type. Attributes may be marked as required by specifying required=”true”, and may also specify a default value. Let’s look at the following sample where I have defined two attributes of type String, Integer.
<aura:component>
    <aura:attribute name="strAttribute" type="String"/>
    <aura:attribute name="intAttribute" type="Integer" required="true" default="0"/>
    <ui:button aura:id="button" press="{!c.increment}" label="{!v.intAttribute ? v.intAttribute : 0}"/>
</aura:component>
({
    increment : function(c,e,h){
        var prev = c.get("v.intAttribute");
        c.set("v.intAttribute", prev ? prev+1 : 1);
    }
})
From the above example
  1. All attributes have a name and a type. Attributes may be marked as required by specifying required=”true”, and may also specify a default value.

        <aura:attribute name="intAttribute" type="Integer" required="true" default="0"/
    
  2. Must begin with a letter or an underscore
  3. Must contain only alphanumeric or underscore characters
  4. You can set default values
  5. Make it as required
  6. You can access the attributes by using the {!expression}  syntax .for example {!v.intAttribute} allow you to access the intAttribute attribute .

Now, let’s go deep into the different types of attributes and how we will be able to use them in the lightning components.

Basic Attributes Type 

The component supports different types of attributes basic types like Boolean, Integer, String, Number etc and Object type and Complex types like functions and Facets.  Here are the supported basic type values. Some of these types correspond to the wrapper objects for primitives in the apex.

<aura:component>
    <aura:attribute name="typeBoolean" type="Boolean" default="false"/>
    <aura:attribute name="typeDate" type="Date" default="2013-03-06T10:17:36.789Z"/>
    <aura:attribute name="typeDateTime" type="DateTime"  default="123456789" />
    <aura:attribute name="typeDecimal" type="Decimal" default="122.62" />
    <aura:attribute name="typeDouble" type="Double" default="12.1"/>	
    <aura:attribute name="typeLong" type="Long"  default="123"/>
    
    <aura:attribute name="typeMap" type="Map"  default="{ a: 1, b: 2 }"/>
    <aura:attribute name="typeInteger" type="Integer" default="1"/>
    <aura:attribute name="typeString" type="String" default="welcome to salesforce"/>
  
    
    Boolean - {!v.typeBoolean} <br/>
    Date - {!v.typeDate}<br/>
    DateTine - {!v.typeDateTime}<br/>
   
</aura:component>

Once you define the attributes you can able to access by using   {!v.<attribute>} expression syntax as shown below.

<aura:attribute name="number" type="String" default="100000"/>
<ui:inputNumber label="Number" value="{!v.number}"/>

Function Type

 An attribute can have a type corresponding to a JavaScript function. If a child component has an attribute of this type, you can pass a callback from a parent component that contains the child component. You will be able to use function type when you’re returning the asynchronous data from the aura:method callbacks.
<aura:attribute name="callback" type="Function" />

In the below example, The echo aura:method has an aura:attribute with a name of the callback. This attribute enables you to set a callback that’s invoked by the aura:method after execution of the server-side action.

  <aura:method name="echo"
      description="Sample method with server-side call">
        <aura:attribute name="callback" type="Function" />
    </aura:method>
  var params = event.getParam('arguments');
        var callback;
        if (params) {
            callback = params.callback;
        }
'// Check the aura action status 
if action is success then return the callback
  if (callback) callback(response.getReturnValue());

Object Types

An attribute can have a type corresponding to an Object. For example:

<aura:attribute name="data" type="Object" />

Salesforce recommends using type=”Map” instead of type=”Object” to avoid some deserialization issues on the server. You could able to iterate the object as shown below.

 <aura:attribute name="data" type="Object"/>
    <aura:iteration items="{!v.data}" var="item">
        {!item.fieldName} -- {!item.newValue} <br/>
    </aura:iteration>

Collection Types 

An attribute can have a type corresponding to like the set, list or Map. you can use aura:iteration to iterate over the collections.

 <aura:attribute name="typeList" type="List" default="[1,2,3]"/>
    <aura:attribute name="typeStringList" type="String[]" default="['red','green','blue']" />
    <aura:attribute name="typeMapList" type="Map[]"/>
    <aura:attribute name="typeSet" type="Set" default="[1,2,3]"/>
    <aura:attribute  name="typeObject" type="Object" default="['red','green','blue']"/>
	
  Map - {!v.typeMap}<br/>
    Set  - {!v.typeSet[0]}<br/>
    List - {!v.typeList[0]}<br/>
    Object - {!v.typeObject}<br/>

Custom Apex types 

An attribute can have a type corresponding apex wrapper class as an attribute type.

public class ApexClassType {
    @AuraEnabled
    public String message{get;set;}
    @AuraEnabled
    public String warning{get;set;}
    
}

You will be able to refer the apex class as attribute as shown below.

    <aura:attribute name="wrapper" type="ApexClassType"/> 

Facet  Types

A facet is an attribute of type Aura.Component[]. The body attribute is an example of a facet. in this example, I defined two Aura.component[]  attributes which are passed when the component is instantiated. FacetDef component contains the title and footer attributes that are passed when components are instantiated from the FacetWrapper component.

FacetDef.cmp

<aura:component >
    <aura:attribute name="title" type="Aura.component[]" />
    <aura:attribute name="footer" type="Aura.component[]"/>
    <section>
        <div><span></span>{!v.title}</div>
        <div><span> </span>{!v.body}</div>
        <div><span></span>{!v.footer}</div>
    </section>
    
</aura:component>

FacetWrapper.cmp

<aura:component >
    <c:FacetDef aura:id="f1">
        <aura:set attribute="title">
            <ui:outputText value="Salesforce Lightning Component Title "/>
        </aura:set>
        <aura:set attribute="body">
            <ui:outputText value="Salesforce Lightning Component Body"/>
        </aura:set>
        <aura:set attribute="footer">
            <p> Copyright 2017 @{!v.org.Name}  - {!v.org.Street} , {!v.org.City} ,{!v.org.State} ,{!v.org.Country} </p>
        </aura:set>
    </c:FacetDef>
    
</aura:component>

Aura Action Types

An Aura.Action is a reference to an action in the framework. If a child component has an Aura.Action attribute, a parent component can pass in an action handler when it instantiates the child component in its markup. This pattern is a shortcut to pass a controller action from a parent component to a child component that it contains and is used for on*handlers, such as onclick.
AuraAction.cmp
<aura:component>
    <aura:attribute name="pressButton" type="Aura.Action" required="true"/>
    <aura:attribute name="pressDiv" type="Aura.Action" required="true"/>
    
    <ui:button aura:id="theButton" label="Press Me" press="{!v.pressButton}"/>
    <div aura:id="theDiv" onclick="{!v.pressDiv}">Click Me</div> 
</aura:component>

Invoke.cmp

aura:component>
    <aura:attribute name="push" type="Boolean" default="false"/>
    <div aura:id="displayResult">No action has run yet</div>
    <c:AuraAction aura:id="fixture" pressButton="{!c.pressButton}"  pressDiv="{!c.pressDiv}"/>
    
</aura:component>

 

({
    pressDiv: function(cmp, event) {
        var div = cmp.find("displayResult");
        if (div) {
            div.getElement().innerHTML = "Div Action is Presed";
        }
    },
    pressButton: function(cmp, event) {
        var div = cmp.find("displayResult");
        if (div) {
            div.getElement().innerHTML = "Button Action is Pressed";
        }
    },
    
})

Inheritance

A subcomponent that extends a super component inherits the attributes of the super component. Attribute values are identical at any level of extension. There is an exception to this rule for the body attribute, which we’ll look at more closely soon. In this example, I defined an attribute with name parentStr on the parent component. when child component extends the parent component it will inherit the parentStr attribute form the parent component without defining it again same as like apex or java.

Parent.cmp

<aura:component extensible="true">
    <aura:attribute name="parentStr" type="String" default="Parent Component Description" />
    {!v.parentStr}  
</aura:component>

SubCmp.cmp

<aura:component extends="c:Parent">
    <p>sub.cmp description: {!v.description}</p>
</aura:component>

when child component extends the parent component it will inherit the parent component attribute as shown above.

Interface

Other OOP programming like Java, Apex etc lightning component has Object-oriented support to define the interface that defines a set of signatures. the below example contains the set of attributes which you could use when you implement this interface.

<aura:interface >
    <aura:attribute name="instanceId" type="Integer" default="-1"/>
    <aura:attribute name="alias" type="String"    default="panel2"/>
    <aura:attribute name="title" type="String"    default="Default Title"/>
    <aura:attribute name="visible" type="Boolean" default="false"/>
    <aura:attribute name="active" type="Boolean" default="false"/> 
</aura:interface>

when a component implements an interface all attributes, methods, and events will be available by the compiler in the background, so the attributes will be available at runtime – say no need to really ‘implement’  from the interface by yourself as shown below.

<aura:component implements="c:panelType">
    <div class="{!(v.visible ? 'visible' : '') + (v.active ? ' active' : '')}">
        <p>{!v.title}</p>
    </div>
</aura:component>

Calling Apex Methods In Lightning web components

Let’s discuss here how to call the apex class from the Lightning web components. Lightning web components can import methods from Apex classes into the JavaScript classes using ES6 import. Once after importing the apex class method you can able call the apex methods as functions into the component by calling either via the wire service or imperatively. Before you use an Apex method, make sure that there isn’t an easier way to get the data. See whether a base Lightning component, like lightning-record-form, lightning-record-view-form, or lightning-record-edit-form works for your use case. If they don’t give you enough flexibility, use a wire adapter like getListUi or getRecordUi. 

Refer this link for how to configure the Salesforce dx for web components 

Import Syntax 

You can able use default export syntax to import an Apex method via the @salesforce/apex scoped module into JavaScript controller class. The Syntax looks like below.

import apexMethod from '@salesforce/apex/Namespace.Classname.apexMethod';

apexMethod—The imported symbol that identifies the Apex method.
Namespace—The namespace of the Salesforce organization. Specify a namespace unless the organization uses the default namespace (c), in which case don’t specify it.
Classname—The name of the Apex class.

Create Apex Class 

In this example, we will be getting account data and show it into the UI. Create an apex class using SFDX create apex class command.

sfdx force:apex:class:create -n GetAccountData -d force-app/main/default/apex

Here is the apex class. To expose an Apex method to a Lightning web component, the method must be static and either global or public. Annotate the method with @AuraEnabled

public with sharing class GetAccountData {
   @AuraEnabled(cacheable=true)
    public static List<Account> getAccountList() {
        return [SELECT Id, Name,Type,Rating,Phone FROM Account];
    }
}

Now you can able to call the apex class in  Lightning web component using these different ways.

  • Wire a property
  • Wire a function
  • Call a method imperatively

Wire an Apex Method to a Property

If an Apex method is annotated with @AuraEnabled(Cacheable=true), you can invoke it from a component via the wire service. You can @wire a property or a function. Here is the syntax

import apexMethod from '@salesforce/apex/Namespace.Classname.apexMethod';
@wire(apexMethod, { apexMethodParams })
propertyOrFunction;

Create a Lightning web component using below SFDX commands

sfdx force:lightning:component:create --type lwc -n LWCWireEx -d force-app/main/default/lwc

Here is the LWCWireEx.html markup for the lightning web components.

<template>
    <lightning-card title="Apex Class Example" icon-name="custom:custom63">
            <div class="slds-m-around_medium">
                <template if:true={accounts.data}>
                    <template for:each={accounts.data} for:item="acc">
                        <p key={acc.Id}>{acc.Name}</p>
                    </template>
                </template>
                
            </div>
        </lightning-card>
</template>

Here is the LWCWireEx.js class

import { LightningElement, wire } from 'lwc';
import getAccountList from '@salesforce/apex/GetAccountData.getAccountList';

export default class LWCWireEx extends LightningElement {
    @wire(getAccountList) accounts;
}

Here is the LWCWireEx.js-meta.xml markup.

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>45.0</apiVersion>
    <isExposed>false</isExposed>
    <targets>
        <target>lightning__RecordPage</target>
       </targets>
</LightningComponentBundle>

Push the changes to scratch org and add the lightning web component to the record page. You can able to see the result like below.

Wire an Apex Method to a Function

Now let’s look at how to wire an apex method to function.we will be updating the same code here to operate the apex method as function.

Update the LWCWireEx.js class as shown below

import { LightningElement, wire,track } from 'lwc';
import getAccountList from '@salesforce/apex/GetAccountData.getAccountList';

export default class LWCWireEx extends LightningElement {
    @track accounts;
    @track error;
    @wire(getAccountList)
    wiredAccounts({ error, data }) {
        if (data) {
            this.accounts = data;
        } else if (error) {
            console.log(error);
            this.error = error;
        }
    }
}

update the LWCWireEx.html markup as shown below

<template>

    <lightning-card title="Apex Wire To Function Example" icon-name="custom:custom63">

        <div class="slds-m-around_medium">
            <template if:true={accounts}>
                 <ul>
                <template for:each={accounts} for:item="account">
                    <li key={account.Id}> {account.Name} </li>
                </template>
             </ul>
            </template>
            <template if:true={error}>
                {error}
            </template>                
        </div>
    </lightning-card>
</template>

Push the changes to scratch org and add the lightning web component to the record page. You can able to see the result like below.

 

Call an Apex Method Imperatively

Now let’s see here how to call apex method imperatively. Create a new Lightning web component using the below SFDX command

sfdx force:lightning:component:create --type lwc -n ImperativEx -d force-app/main/default/lwc

Use the below ImperativEx.html code

<template>
        <lightning-card title="Apex Imperative Method Example">
            <div class="slds-m-around_medium">
                <p class="slds-m-bottom_small">
                    <lightning-button label="Load Accounts" onclick={handleLoad}></lightning-button>
                </p>
                <template if:true={accounts}>
                    <template for:each={accounts} for:item="account">
                        <p key={account.Id}>{account.Name}</p>
                    </template>
                </template>
                <template if:true={error}>
                   {error}
                </template>
            </div>
        </lightning-card>
    </template>

Use the below ImperativEx.js class code

import { LightningElement, wire,track } from 'lwc';
import getAccountList from '@salesforce/apex/GetAccountData.getAccountList';


export default class ImperativEx extends LightningElement {
    @track accounts;
    @track error;
    handleLoad() {
        getAccountList()
            .then(result => {
                this.accounts = result;
            })
            .catch(error => {
                this.error = error;
            });
    }
}


Use the below ImperativEx.js-meta.xml class code

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata" fqn="hello">
    <apiVersion>45.0</apiVersion>
    <isExposed>false</isExposed>
     <targets>
        <target>lightning__RecordPage</target>
    </targets>
</LightningComponentBundle>

push the changes to scratch org and add the lightning web component to the record page and you can able to see the below result when you click on the button .

Lightning web components Data Binding in a Template

Let us discuss here how using the Data Binding in a Template. Templating allows binding values from the JavaScript controller value to the Html view in Lightning web component. In Lightning web component, this can be achieved using simple {property} bindings. Bind properties in a component’s template to properties in the component’s JavaScript class. In the template, surround the property with curly braces, {property}. To compute a value for the property, use a JavaScript getter in the JavaScript class, get property(){}. Don’t write JavaScript expressions in a component’s template. Refer to this link for how to set up the environment.

Using Expressions

Here’s the simplest example of data binding using an expression. The greeting property in the template is bound to the greeting property in the JavaScript class.

<!-- hello.html -->
<template>
    Hello, {greeting}!
</template>
// hello.js
import { LightningElement } from 'lwc';

export default class Hello extends LightningElement {
    greeting = 'World';
}

Now in the above example, the greeting property is blinded from the controller using the {greeting} binding expression to UI. The property in { } must be a valid JavaScript identifier or member expression.

Now its understand how to bind the expression from the input value instead of a hard-coded string.update the markup as shown below

<template>
    <p>Hello, {greeting}!</p>
    <lightning-input label="Name" value={greeting} onchange={handleChange}></lightning-input>
</template>

update the JavaScript controller as shown below.

import { LightningElement, track } from 'lwc';

export default class HelloBinding extends LightningElement {
    @track greeting = 'World';

    handleChange(event) {
        this.greeting = event.target.value;
    }
}

If you look at the above JavaScript controller we are using the event handling in the JavaScript class will pass the event to handleChange method. The event object contains information about the change event. The component uses the event object to get the value that the user entered into the input field. we are using @track decorator to track the greeting propriety. Here is the output

Using Getter 

We will be using javascript getter to compute a value for a property. For example, to convert the name to all uppercase letters, use a getter function in the JavaScript class, not an expression in the template. The getter is much more powerful than expressions because they’re JavaScript functions. Define the Getter as shown below syntax.

get propertyName() { ... }

Access the getter from the template using the below syntax

{propertyName}

In this example, a user enters their first and last name. A JavaScript getter computes a new value and updates the value to the upper case. Update the markup as shown below.

<template>
    <div class="slds-m-around_medium">
        <lightning-input name='firstName' label="First Name" onchange={handleChange}></lightning-input>
        <lightning-input name='lastName' label="Last Name" onchange={handleChange}></lightning-input>
        <p class="slds-m-top_medium">Uppercased Full Name: {uppercasedFullName}</p>
    </div>
</template>

The uppercasedFullName property in the template is bound to the get uppercasedFullName() getter in the JavaScript class. here is the JavaScript class

import { LightningElement, track } from 'lwc';

export default class HelloExpressions extends LightningElement {
    @track firstName = '';
    @track lastName = '';

    handleChange(event) {
        const field = event.target.name;
        if (field === 'firstName') {
            this.firstName = event.target.value;
        } else if (field === 'lastName') {
            this.lastName = event.target.value;
        }
    }

    get uppercasedFullName() {
        return `${this.firstName} ${this.lastName}`.toUpperCase();
    }
}

The handleChange function sets the firstName and lastName properties to the values that the user enters. The uppercasedFullName() getter combines and uppercases the names and update the value to UI. You can see in JavaScript class we are using @track decorator with firstName and lastName Because firstName and lastName decorated with @track, if the value of firstName or lastName changes, the template rerenders. If we remove @track, the property values still change, but the component doesn’t re-render, so we don’t see update values in UI. Decorating a property with @track makes it private and reactive. To mark a property public and reactive, use @api.Here is the final output.

Here is another example that will get the data from the contact record and shows the data.

<template>
        <lightning-card title="My Contact Record" icon-name="standard:contact">
            <template if:true={contact.data}>
                <div class="slds-m-around_medium">
                        <lightning-output-field value={firstName} name='firstName' label="First Name"></lightning-output-field>
                        <lightning-output-field value={lastName} name='lastName' label="Last Name" >
                        </lightning-output-field>
                        <p class="slds-m-top_medium">Uppercased Full Name: {uppercasedFullName}</p>
                </div>
            </template>
            
        </lightning-card>
</template>
import { LightningElement, api, wire ,track} from 'lwc';
import { getRecord } from 'lightning/uiRecordApi';
const fields = [
    'Contact.FirstName',
    'Contact.LastName',
];
export default class HelloExpressions extends LightningElement {
     @api recordId;
     @wire(getRecord, { recordId: '$recordId', fields })
     contact;
     get firstName() {
        return this.contact.data.fields.FirstName.value;
    }

    get lastName() {
        return this.contact.data.fields.LastName.value;
    }
    get uppercasedFullName() {
        return `${this.contact.data.fields.FirstName.value} ${this.contact.data.fields.LastName.value}`.toUpperCase();
    }
}

 

Flow Local Actions Using Lightning Components

                    Flow Local Actions Using Lightning Components allow you to execute client-side logic in your flow, build or modify custom Lightning components to use as local actions in flows. For example, get data from third-party systems without going through the Salesforce server, or open a URL in another browser tab. Once you configure the Lightning component’s markup, client-side controller, and design resource, it appears in the Cloud Flow Designer as a Local Action element. In this example, the input screen on the flow will take a country name and from there invoke the local action and return the country information from the web service.

How to add Flow Local Actions 

          Add the lightning:availableForFlowActions interface to a Lightning component to make it available as a flow action. When a component is executed as a flow action, the flow calls the invoke method in the client-side controller. Flows that include Lightning components are supported only in Lightning runtime. Lightning components that implement lightning:availableForFlowActions are available as Local Action elements in the Cloud Flow Designer. When a component is executed as a flow local action, the flow calls the invoke method in the client-side controller. To run the code asynchronously in your client-side controller, such as when you’re making an XML HTTP request (XHR), return a Promise. When the method finishes or the Promise is fulfilled, control is returned back to the flow.

Flow Action Component 

Here is the simple component that we will be used as flow action.
<aura:component implements="lightning:availableForFlowActions" access="global">
    <aura:attribute name="countryName" type="String" default="USA" access="global" />
    <aura:attribute name="response" type="String" default="USA" access="global" />
</aura:component>
Controller 
({
    invoke : function(component, event, helper) {
        var cancelToken = event.getParam("arguments").cancelToken;
        return new Promise(function(resolve, reject) {
            var xhttp = new XMLHttpRequest();
            xhttp.onreadystatechange = $A.getCallback(function() {
                if (this.readyState === 4) { 
                    if (this.status >= 200 && this.status < 300) {
                        var response = JSON.parse(xhttp.responseText);
                        console.log('response'+response);
                        component.set("v.response", JSON.stringify(response));
                        resolve();
                    } else {
                        var errorText = "";
                        if (this.status === 0) {
                            errorText = 'Request has been terminated\nPossible causes: the network is offline, Origin is not allowed by Access-Control-Allow-Origin, the page is being unloaded, etc.';
                        } else {
                            errorText = this.statusText;
                        }
                        reject(errorText);
                    }
                }
            });
            var countryName = component.get("v.countryName");
            xhttp.open("GET", "https://restcountries.eu/rest/v2/name/"+countryName, true);
            xhttp.send(null);
            cancelToken.promise.then(function(error) {
                xhttp.abort();
                reject(error);
            });
        });
    }
})
When a component is executed as a flow local action, the flow calls the invoke method in the client-side controller. To run the code asynchronously in your client-side controller, such as when you’re making an XML HTTP request (XHR), return a Promise. When the method finishes or the Promise is fulfilled, control is returned back to the flow. When a Promise is resolved, the next element in the flow is executed. When a Promise is rejected or hits the timeout, the flow takes the Local Action element’s fault connector and sets $Flow.FaultMessage to the error message. By default, the error message is “An error occurred when the elementName element tried to execute the c:FlowAction component.” To customize the error message in $Flow.FaultMessage, return it as a new Error object in the reject() call. When a Promise is fulfilled, the next element in the flow is executed. When a Promise is rejected or times out, the flow takes the Local Action element’s fault connector and sets $Flow.FaultMessage to the error message. By default, the error message is “An error occurred when the flow tried to execute the c:FlowAction component.”. To customize the error message, return it as an error in the reject() call.

Configure the designer 

To make an attribute’s value customizable in the Cloud Flow Designer, add it to the component’s design resource. That way, flow admins can pass values between that attribute and the flow when they configure the Local Action element. That way, flow admins can pass values between that attribute and the flow when they configure the corresponding Local Action element.

<design:component>
    <design:attribute name="countryName" label="String"  />
    <design:attribute name="response" label="String"  />
</design:component>

Invoke Action from the Flow 

Now go to the cloud flow designer and create a new screen element to enter the country name

Add the Flow locale actions into the screen and configure the input params as shown below.

Create a screen element to show the output response as shown below.
Final screen flow looks like below

Testing 

Synchronous Code

the example code we have seen so far is Asynchronous Code where we will get the promise and here is the synchronous version of the controller.js

({
    invoke : function(component, event, helper) {
        var cancelToken = event.getParam("arguments").cancelToken;
        var xhttp = new XMLHttpRequest();
        var countryName = component.get("v.countryName");
        xhttp.open("GET", "https://restcountries.eu/rest/v2/name/"+countryName, false);
        xhttp.send(null);
        if (xhttp.status === 200) {
            component.set("v.response", JSON.stringify(xhttp.responseText));
        }
        
    },
    
})