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. In This post we will talk about how Invoke Apex Controller From Lightning Web Component and how many way we have to call apex class from LWC. 

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 {
          @track accounts;
          @track error;
         
          @wire( findAccounts )
          wiredAccounts({data, error}){
                  if(data){
                      this.accounts = data;
                      this.error = undefined;
                  }
                  else if (error) {
                      this.error = error;
                      this.accounts = undefined;
                  }
              }
      }
      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;
                    });

            }
        }
    }


Wire Vs Imperatively

As per Lightning component best practices use @wire over imperative method invocation. @wire fits nicely in the overall Lightning Web Component reactive architecture. Salesforce is building some performance enhancement features that are only available with @wire. But there are a few use cases,, that require you to use imperative Apex.

Wire Property Vs Wire Function :

 

Prefer wiring to a property. This best practice applies to @wire in general (not just to wiring Apex methods).


Please check this post to learn about Lightning web component best practices. 


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

5 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
  2. I have a wrapper class which has variables of type string and number, I have created a LWC form and using event.detail.value I have collected all information and need to pass that to call the apex method however the type of object in javascript is object I am trying to strinify that and send that as parameter I am getting a null pointer in debug logs? any idea or any example for the same?

    ReplyDelete
  3. Hello, I guess you have to change 3.1 example to agree with 3.2, this is, to wird a function but without parameter. I guess 3.1 has by error the same example than 1.1

    Gracias por el ejemplo. Ha sido de mucha ayuda.

    J. F.

    ReplyDelete