LWC communication with Apex and Salesforce

Всім привіт! Це вже наша четверта та фінальна стаття з серії по LWC. У попередніх ми розбирались з вами у принципах побудови компонентів, базових принципах та особливостях розмітки у LWC та комунікації наших компонентів між собою.

У цій же статті ми пропонуємо вам розглянути як можна використовувати вбудовані методи LWC для звернення до Apex-контролерів та отримання даних з бази даних Salesforce. Давайте розпочнемо!

Для прикладу використаємо базовий апекс контролер, у якому є метод getContacts, що повертає нам список контактів.

public with sharing class ContactController {
    @AuraEnabled(cacheable=true)
    public static List<Contact> getContacts() {
        return [SELECT Id, FirstName, LastName, Email FROM Contact];
    }
}

Для звернення до цього методу можна використовувати вбудований метод @wire в LWC. Ця анотація використовується для виконання запитів до Salesforce, таких як отримання даних з об’єктів або виконання пошуку. Вона дозволяє пов’язувати результати запитів з властивостями компонента. Ось приклад коду, який використовує метод @wire для отримання даних з Apex-контролера та виводить їх на екран:

import { LightningElement, wire } from 'lwc';
import getContacts from '@salesforce/apex/ContactController.getContacts';

export default class ContactList extends LightningElement {
    @wire(getContacts)
    contacts;

    get hasContacts() {
        return this.contacts && this.contacts.data && this.contacts.data.length > 0;
    }
}

У цьому прикладі, ми використовуємо декоратор @wire для звернення до методу getContacts в Apex-контролері ContactController. Отримані дані зберігаються в змінній contacts. У шаблоні використовується геттер hasContacts, який повертає true, якщо знайдені контакти.

Також цю анотацію можна використовувати для прямої взаємодії з Salesforce. У цьому прикладі ми використаємо анотацію @wire для виклику функції getRecord з пакета lightning/uiRecordApi. Функція getRecord виконує запит до Salesforce для отримання запису з заданим ID та зазначеними полями.

Результат запиту зберігається в змінній account, яку ми можемо використовувати у нашому компоненті. У методі get accountName() ми використовуємо значення поля Name з отриманого запису.

import { LightningElement, wire } from 'lwc';
import { getRecord } from 'lightning/uiRecordApi';
export default class ExampleComponent extends LightningElement {
    @wire(getRecord, { recordId: '001xxxxxxxxxxxxxxx', fields: ['Account.Name'] })
    account;
    get accountName() {
        return this.account.data.fields.Name.value;
    }
}

@wire анотація має в собі багато можливостей, однак давайте ще розберемо також ЇЇ використання для отримання picklist значень об’єктів Salesforce.

import { LightningElement, wire } from 'lwc';
import { getObjectInfo, getPicklistValues } from 'lightning/uiObjectInfoApi';
import ACCOUNT_OBJECT from '@salesforce/schema/Account';
import INDUSTRY_FIELD from '@salesforce/schema/Account.Industry';

export default class PicklistValuesExample extends LightningElement {
    industryPicklistValues = [];

    @wire(getObjectInfo, { objectApiName: ACCOUNT_OBJECT })
    accountObjectInfo;

    @wire(getPicklistValues, { recordTypeId: '$accountObjectInfo.data.defaultRecordTypeId', fieldApiName: INDUSTRY_FIELD })
    industryPicklist({ data, error }) {
        if (data) {
            this.industryPicklistValues = data.values;
        } else if (error) {
            // Error handling
        }
    }
}

У цьому прикладі @wire анотація використовується для отримання пікліст значень поля “Industry” об’єкта “Account”. Спочатку ми використовуємо getObjectInfo для отримання інформації про об’єкт, включаючи тип запису за замовчуванням. Потім ми використовуємо getPicklistValues для отримання пікліст значень для поля “Industry”. Отримані значення зберігаються у змінній industryPicklistValues.

Цей приклад показує, як @wire анотація дозволяє легко та ефективно отримувати пікліст значень полів об’єктів Salesforce для використання у вашому Lightning компоненті.

Також існує інший спосіб виклику методів Apex з LWC, який не використовує декоратор @wire. Для цього можна використовувати імпорт методу та його виклик у функції компонента LWC. Давайте розглянемо, як це можна зробити.

Повернемось до нашого Apex контролера та методу getСontacts, але цього разу викличемо його без використання декоратора @wire:

import { LightningElement } from 'lwc';
import getContacts from '@salesforce/apex/ContactController.getContacts';

export default class ContactList extends LightningElement {
    contacts;

    connectedCallback() {
        getContacts()
            .then(result => {
                this.contacts = result;
            })
            .catch(error => {
                console.error(error);
            });
    }
}

У цьому прикладі, ми імпортуємо метод getContacts з Apex-контролера ContactController. У функції connectedCallback, яка викликається, коли компонент додається у DOM, ми викликаємо цей метод та отримуємо дані з Salesforce. Отримані дані зберігаються у змінній contacts.

Цей підхід не використовує декоратор @wire, але також дозволяє викликати метод Apex з LWC та отримувати дані з бази даних Salesforce. Ви можете вибрати підхід, який найкраще підходить для вашого проекту та вашого стилю програмування.

Тепер ми знаємо що у LWC є два способи виклику методів Apex з LWC: з анотацією @wire та без неї. Обидва підходи мають свої переваги та недоліки.

Переваги використання @wire для виклику методів Apex з LWC:

  • @wire автоматично обробляє запити до сервера та оновлює дані, коли вони змінюються.
  • Можна використовувати @wire для виконання запитів до сервера при запуску компонента LWC.
  • @wire можна використовувати для передачі даних між компонентами LWC.
  • Однією з додаткових переваг використання @wire є можливість автоматичного кешування даних, що може покращити продуктивність компонентів.

Недоліки використання @wire:

  • wire використовується для асинхронного отримання даних з сервера. Це означає, що дані можуть приходити з сервера з певною затримкою. Тобто ми не можемо гарантувати послідовність викликів різних @wire методів. У порівнянні з прямим імпортом та викликом, у яких можна викликати методи апекс послідовно один з одного. Таким чином, якщо ми планували використовувати результати відповіді одного методу в виклику іншого – ми не можемо гарантувати правильність такої роботи при використанні @wire.
  • Іноді @wire може бути складним для налаштування та розуміння, особливо якщо ви працюєте з багатошаровими об’єктами.
  • Обмеження у використанні: Wire має обмеження в тому, які типи даних можуть бути повернуті з Apex класів. Наприклад, Wire не може повертати колекції мап або об’єктів.

Переваги використання простого виклику методу для виклику Apex з LWC:

  • Виклик методу може бути менш складним для розуміння, особливо якщо ви працюєте з простими об’єктами.
  • Також ми можемо контролювати коли саме буде здійснюватись виклик методу. Це може покращити продуктивність вашого додатка, оскільки ви будете отримувати дані лише тоді, коли вони дійсно потрібні.
  • Виклик методу дозволяє вам більш гнучко контролювати дані, які ви передаєте та отримаєте з Apex класу. Ви можете використовувати JavaScript об’єкти для передачі та отримання даних, що дозволяє вам більш гнучко працювати з даними та обробляти їх на клієнтському боці.

Недоліки використання простого виклику методу:

  • Ви повинні вручну імпортувати методи які плануєте використовувати.
  • Вимагає створення apex класу та методів та не може для прикладу використати стандартний функціонал як getRecord

Отже, обидва підходи мають свої переваги та недоліки. Ви можете вибрати підхід, який найкраще відповідає вашим потребам та вимогам проекту.

Незалежно від того, який підхід використовується для виклику методів Apex з LWC, важливо обробляти помилки, які можуть виникнути в процесі виконання запиту.

Ось декілька загальних рекомендацій щодо обробки помилок при використанні LWC для виклику методів Apex:

  • Використовуйте try-catch-блоки для перехоплення помилок, що можуть виникнути під час виконання методів Apex.
  • Інформуйте користувачів про помилки та подальші рекомендовані кроки.
  • Використовуйте систему логування Salesforce для зберігання детальної інформації про помилки, які виникають у процесі виконання методів Apex або LWC.
  • Не відображайте конфіденційну інформацію, таку як stack trace, для користувачів у виробничих середовищах.

Наприклад, при використанні @wire для виклику методів Apex можна використовувати блок catch, щоб обробити помилки. Ось приклад коду:

import { LightningElement, api, wire } from 'lwc';
import getAccounts from '@salesforce/apex/AccountController.getAccounts';

export default class AccountList extends LightningElement {
    @api recordId;
    accounts;
    error;

    @wire(getAccounts, { recordId: '$recordId' })
    wiredAccounts({ error, data }) {
        if (data) {
            this.accounts = data;
            this.error = undefined;
        } else if (error) {
            this.error = error;
            this.accounts = undefined;
        }
    }
}

У цьому прикладі ми використаємо блок catch у методі wiredAccounts, щоб перехоплювати помилки, які виникають у процесі виконання методу getAccounts. Ми також використовуємо змінну error, щоб зберігати інформацію про помилку та відображати її для користувача.

В простому виклику методу ми можемо також використовувати блок catch для обробки помилок. Ось приклад коду:

import { LightningElement, api, wire } from 'lwc';
import getAccounts from '@salesforce/apex/AccountController.getAccounts';

export default class AccountList extends LightningElement {
    @api recordId;
    accounts;
    error;
    loadingSpinner = false;

    handleButtonClick() {
        this.loadingSpinner = true;
        getAccounts({ recordId: this.recordId 
        }).then(result => {
            this.accounts = result;
            this.error = undefined;
        }).catch(error => {
            this.error = error;
            this.accounts = undefined;
        }).finally(() => {
            this.loadingSpinner  = false;
        })
    }
}

У цьому прикладі ми викликаємо метод getAccounts без анотації @wire. Ми використовуємо метод Promise.then та Promise.catch, щоб обробити результати запиту та помилки, які можуть виникнути під час його виконання.
Також ви можете побачити конструкцію finally яку зручно використовувати у випадках коли не зважаючи на результати виконання виклику у Apex (в тому числі не зважаючи на помилки) нам потрібно виконати наступні дії. Хорошим прикладом буде спінер який показує користувачу що в даний момент сторінка обробляє данні і по закінченню він повинен зникнути не зважаючи на помилки які можуть повернутись з Apex. У нашому прикладі ви саме можете побачити таку логіку поводження з loadingSpinner.

Отже, важливо обробляти помилки, які можуть виникнути під час виклику методів Apex з LWC, незалежно від того, який підхід використовується для їх виклику. Обробка помилок може допомогти забезпечити безпеку та стабільність вашого LWC-компонента та додатку загалом.

Також у цій статті хотілось би розібрати один з найпростіших та зручних способів форматування великої кількості даних у апекс перед поверненням їх у LWC.

JSONGenerator - це клас в Apex, який дозволяє побудувати JSON-об’єкт із набору даних, що представлені у вигляді списку або мапи. Це особливо корисно для повернення даних з Apex методу до LWC компонента в структурованому форматі.

Ось приклад, який демонструє використання JSONGenerator для побудови JSON-об’єкта з списку об’єктів Account в Apex:

public class AccountJSONGenerator {

  @AuraEnabled(cacheable=true)
  public static String getAccountsJSON() {

    List<Account> accounts = [SELECT Id, Name, Industry, Type, BillingState FROM Account LIMIT 10];

    JSONGenerator gen = JSON.createGenerator(true);
    gen.writeStartArray();
    for(Account acc : accounts) {
        gen.writeStartObject();
        gen.writeStringField('id', acc.Id);
        gen.writeStringField('name', acc.Name);
        gen.writeStringField('industry', acc.Industry);
        gen.writeStringField('type', acc.Type);
        gen.writeStringField('billingState', acc.BillingState);
        gen.writeEndObject();
    }
    gen.writeEndArray();

    return gen.getAsString();
  }
}

У цьому прикладі метод getAccountsJSON() викликається з LWC, і повертається рядок з JSON, який містить дані зі списку об’єктів Account. Спочатку відбувається запит до бази даних на отримання списку Account об’єктів. Потім використовується клас JSONGenerator для створення JSON-об’єкта, який містить ці дані в структурованому форматі. Останнім кроком є повернення цього JSON рядка з методу getAccountsJSON().

У LWC, ви можете отримати цей JSON-об’єкт через метод Apex та зручно використовувати надалі:

import { LightningElement, wire } from 'lwc';
import getAccountsJSON from '@salesforce/apex/AccountJSONGenerator.getAccountsJSON';

export default class AccountList extends LightningElement {
    accounts;

    @wire(getAccountsJSON)
    wiredAccounts({error, data}) {
        if (data) {
            this.accounts = JSON.parse(data);
        } else if (error) {
            console.error(error);
        }
    }
}

У цьому прикладі, метод getAccountsJSON() з Apex викликається, і дані передаються до змінної data та помилки до змінної error. Перед тим, як data буде встановлено в змінну accounts, його необхідно розпакувати з формату JSON за допомогою функції JSON.parse(). У разі помилки, відображається повідомлення про помилку у консолі браузера.

Отже, використання JSONGenerator в Apex дозволяє створити структурований JSON-об’єкт з об’єктів або списків, який можна повернути у LWC для подальшої обробки.

Отже, ми розглянули різні методи взаємодії LWC з Apex та Salesforce, а саме:
Використання декоратора @wire для отримання даних з Apex методу. Виклик методу Apex з LWC попередньо імпортуючи ці методи. Ці методи мають свої переваги та недоліки, тому важливо враховувати контекст вашого проекту та вибрати найбільш оптимальний спосіб взаємодії між LWC та Apex.Також, важливо дотримуватися best practice при використанні цих методів, таких як перевірка помилок та оптимізація запитів до сервера. Ми також розглянули, як використовувати JSONGenerator в Apex для підготовки структурованого JSON-об’єкта з об’єктів або списків, який можна повернути в LWC для подальшої обробки. Це фінальна стаття з серії по основам LWC. Раніше ми розглянули основи LWC компонентів, базові принципи розмітки у LWC та комунікації між LWC. Сподіваємось вам сподобалось та вони були для вас корисні.

2 Вподобання

Ще ніколи не бачив використання JSONGenerator у “бойовому” коді. До того ж він виглядає як монструозна незграба )))

Замість цього краще використовувати врапери (Wrapper Class, як класи першого рівня так і внутрішні, що навіть краще). До того ж серіалізацію ми отримуємо за замовчуванням.

Крім того, також можу порекомендувати використовувати Promise.finally у випадку коли якась логіка повинна бути виконана незалежно від наявності помилок. Наприклад прибрати спіннер.

1 Вподобання

Так погоджуюсь врапер класи зручніші у використанні однак тут було прийняте рішення продемонструвати підхід який швидше за все не використовувався розробником раніше під час розробки з Apex.
Про finall дякую це також має сенс покрити у цій статті. Я оновив статтю згадавши про нього :slight_smile: дякую за коментар.

1 Вподобання