Friday, 21 December 2018

Invoke Apex Controller from Lightning Web Component | Lightning Web Component inside Another LWC


Lightning Web Components are announced as part of Spring19 pre-release. In out last most we talk about how to create first Lightning Web Component and How to call an Apex class from a Lightning Web component.

In This post we will talk about how Invoke Apex Controller From Lightning Web Component  and How to call one Lightning Web Component in another Lightning Web Component.

Before Starting the code let understand some basic concept :-



1) Decorators
The Lightning Web Components programming model has three decorators that add functionality to a property or function
  • @api:- To expose a public property, decorate it with @api
  • @track :- To track a private property’s value and rerender a component when it changes, decorate the property with @track.
  • @wire :- To read Salesforce data, Lightning web components use a reactive wire service. When the wire service provisions data, the component rerenders. Components use @wire in their JavaScript class to specify a wire adaptor or an Apex method.
2) Component Folder And Naming Rules:-


Some Best Practice for folder and its files naming :-
  1. Must begin with a letter
  2. Must contain only alphanumeric or underscore characters
  3. Must be unique in the namespace
  4. Can’t include whitespace
  5. Can’t end with an underscore
  6. Can’t contain two consecutive underscores
  7. Can’t contain a hyphen (dash)
Check this post for more detail.




3) Call Apex Class / Method:-

Lightning web components can import methods from Apex classes into the JavaScript classes using ES6 import.

import apexMethod from '@salesforce/apex/Namespace.Classname.apexMethod';
  • apexMethod—This identifies the Apex method name.
  • Classname— The Apex class name.
  • Namespace—The namespace of the Salesforce organization

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. We have three way to call Apex method
  1. Wire a property
  2. Wire a function
  3. Call a method imperatively.

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 .


Lets talk about all three method to invoke a method

  1. Wire a property : There are two way to call apex method as wire property:
    1. Apex Wire Method to Property  Syntax :
      import { LightningElement, wire  } from 'lwc';
      import findAccounts from '@salesforce/apex/AccountController.findAccounts';
      export default class SearchAccountRecords extends LightningElement {
          @wire( findAccounts ) accounts;
      }
      As per the wire method writing rules, first we have imported method (findAccounts) from the Apex Class (AccountController). Then, property accounts is wired to findAccounts method in below format :-
      @wire( findAccounts ) accounts;
      Now accounts is the property of wiring, that means we can get the data of account using {accounts.data} & error using {accounts.error} in html file.
    2. Apex Wire Method to Property with Parameters  Syntax :

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

      @wire(apexMethod, { apexMethodParams }) propertyOrFunction;
      If we need to pass param in above js file then syntex should be like below :-
      @wire( findAccounts , {accountName:'$searchKey'} ) accounts;Here  “searchkey” is param. During passing of the parameters, we need to put ‘$’ sign before the property.
  2. Wire a function : This mechanism is needed if you want to pre-process the data before going to the Lightning App. There are two way to call apex method as wire function
    1.  Apex Wire Method to Function
       Syntax :
      import { LightningElement, wire  } from 'lwc';
      import findAccounts from '@salesforce/apex/AccountController.findAccounts';
      export default class SearchAccountRecords extends LightningElement {
          @wire( findAccounts ) accounts;
      }
      As per the wire method writing rules, first we have imported method (findAccounts) from the Apex Class (AccountController). Then, property accounts is wired to findAccounts method in below format :-
      @wire( findAccounts ) accounts;
    2.  Apex Wire Method to Function with Parameters 
       Syntax :
      import { LightningElement, wire, track } from 'lwc';
      import findAccounts from '@salesforce/apex/AccountController.findAccounts';
      export default class SearchAccountRecords extends LightningElement {
          @track accounts;
          @track error;
          @wire(findAccounts , {accountName:'$searchKey'} )
              wiredContacts({data, error}){
                  if(data){
                      this.accounts = data;
                      this.error = undefined;
                  }
                  else if (error) {
                      this.error = error;
                      this.accounts = undefined;
                  }
              }
      }
      There will be only one change we need to pass all param with $ sign.
  3. Call a method imperatively : If an Apex method is not annotated with cacheable=true, you must call it imperatively
     Syntax :
    import { LightningElement, track } from 'lwc';
    import findAccounts from '@salesforce/apex/AccountController.findAccounts';
    export default class SearchAccountRecords extends LightningElement {
        @track accounts;
        @track error;
        handleKeyChange(event) {
                findAccounts({ searchKey })
                    .then(result => {
                        this.accounts = result;
                        this.error = undefined;
                    })
                    .catch(error => {
                        this.error = error;
                        this.accounts = undefined;
                    });

            }
        }
    }
    There will be only one change we need to pass all param with $ sign.


Lets take a deep drive for one method "Call a method imperatively"


Step 1) Create Apex Class with @AuraEnabled annotation.

public with sharing class AccountController {
    @AuraEnabled(cacheable=true)
    public static List<Account> findAccounts(String searchKey) {
        String key = '%' + searchKey + '%';
        return [SELECT Id, Name, AccountNumber FROM Account WHERE Name LIKE :key  LIMIT 10];
    }
}
Apex method should be static and AuraEnabled.

Step 2) Create Lightning Web Component to show Account Card.

NOTE:- Use camelCase for LWC component Name. 

accountCard.html
<template>
        <lightning-layout vertical-align="center">
            <lightning-layout-item padding="around-small">
                <p>{account.Name}</p>
                <p>{account.AccountNumber}</p>
            </lightning-layout-item>
        </lightning-layout>
</template>

accountCard.js
import { LightningElement, api } from 'lwc';

export default class AccountCard extends LightningElement {
    @api account;
}
  •  The component’s public API via public properties and methods annotated with @api.
accountCard.js-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata" fqn="AccountCard">
    <apiVersion>45.0</apiVersion>
    <isExposed>false</isExposed>
</LightningComponentBundle>

Step 3)  Create Lightning Web Component to Search Account record.

searchAccountRecords.html
<template>
    <lightning-card title="Search Account Hello2" icon-name="custom:custom57">
        <div class="slds-m-around_medium">
            <lightning-input type="search" onchange={handleKeyChange} class="slds-m-bottom_small" label="Search"></lightning-input>
            <template if:true={accounts}>
                <template for:each={accounts} for:item="acc">
                    <c-account-card key={acc.id} account={acc} ></c-account-card>
                </template>
            </template>
        </div>
    </lightning-card>
</template>
  • NOTE:- Is recommended to use camelCase for LWC Name. If file name starts with uppercase it needs to be <c—account-record> per HTML spec.

 searchAccountRecords.js
import { LightningElement, track } from 'lwc';
import findAccounts from '@salesforce/apex/AccountController.findAccounts';
/** The delay used when debouncing event handlers before invoking Apex. */
const DELAY = 350;
export default class SearchAccountRecords extends LightningElement {
    @track accounts;
    @track error;
    handleKeyChange(event) {
        // Debouncing this method: Do not actually invoke the Apex call as long as this function is
        // being called within a delay of DELAY. This is to avoid a very large number of Apex method calls.
        window.clearTimeout(this.delayTimeout);
        const searchKey = event.target.value;
        // eslint-disable-next-line @lwc/lwc/no-async-operation
        this.delayTimeout = setTimeout(() => {
            findAccounts({ searchKey })
                .then(result => {
                    this.accounts = result;
                    this.error = undefined;
                })
                .catch(error => {
                    this.error = error;
                    this.accounts = undefined;
                });
        }, DELAY);
    }
}
  • Import  track element with import { LightningElement, track } from 'lwc';
  • Import your Apex Class Method  "import findAccounts from '@salesforce/apex/AccountController.findAccounts';"
  • Variable name should be same "searchKey" for apex class and js files. Check more detail here.
  • Use decorators to add functionality to a property. A property can have only one decorator. For example, a property can’t have @api and @track (private reactive property) decorators.
searchAccountRecords.js-meta.xml
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata" fqn="searchAccountRecords">
    <apiVersion>45.0</apiVersion>
    <isExposed>false</isExposed>
    <targets>
        <target>lightning__AppPage</target>
        <target>lightning__RecordPage</target>
        <target>lightning__HomePage</target>
    </targets>
</LightningComponentBundle>


Here is recording for this post.
https://www.youtube.com/watch?v=Z6a7IUQYKJs


 Check below post for more detail
1) https://developer.salesforce.com/docs/component-library/documentation/lwc/lwc.apex

Please check our old post on Lightning Web Components:-


Feel free to post your feedback or question.

Thanks,
Amit Chaudhary
amit.salesforce21@gmail.com

2 comments:

  1. Controller doesn't work in system mode is it true. Can you try with other user which has limited access unlike VF page controller it will give error e.g. FLS etc. Can u plz provide solution on this.

    ReplyDelete