Lightning Message Service in LWC

Всім привіт!

У цій статі хочу розказати про Lightning Message Service. Раніше вже виходила стаття на цю тему в якій описувалось що таке Lightning Message Service, для чого він потрібен, про специфіку, особливості та обмеження використання даного сервісу (детальніше ви можете ознайомитись тут). Сьогодні поговоримо більше про те, як це можна використовувати на практиці в Lightning Web Component.

Якщо абстрагуватись від конкретного синтаксису, то Lightning Message Service уособлює поведінковий патерн Observer. Я не буду сильно загострювати на цьому увагу, лише скажу, що це патерн, який створює механізм підписки, що дає змогу одним об’єктам стежити й реагувати на події, які відбуваються в інших об’єктах. Детальніше можете почитати тут.

Перед початком ще трохи теорії про декоратори та комунікацію компонент в LWC, які будуть використовуватись в прикладі. Існує три види декораторів:

  1. track – анотація над змінною, яка вказує приватний тип доступу до неї. Доступ до змінної буде лише в тій компоненті, де вона оголошена.
  2. api – анотація над змінною, яка вказує публічний тип доступу до неї. Такі змінні використовуються для передачі даних з parent до child.
  3. wire – анотація, яка робить змінну реактивною і забезпечує незмінний потік даних.

Також, існує три типи комунікації між компонентами:

  • Child-to-Parent
  • Parent-to-Child
  • Комунікація між непов’язаними компонентами

Перші два типи можна реалізувати без використання message service, а ось альтернатив для комунікації на клієнті між непов’язаними компонентами нема.

Child-to-Parent можна реалізувати dispatchEvent().
Наприклад:
Child.js

throwDataToParent (data) {
   
    const customEvent = new CustomEvent('throw-data-to-parent', {
        detail: {
            childData: data
        }
    });
 
    this.dispatchEvent(customEvent);
}

Parent.html

<template>
    <div class="container slds-p-around_large slds-theme_shade">
        <div class="slds-float_right">
            <c-child onchildevent={handleChildEvent}></c-child>
        </div>
    </div>
</template>

Parent.js

handleChildEvent (event) {
    console.log(event.detail.childData);
}

Якщо потрібно передати дані з parent до child, то змінну в child компоненті декоруємо api, а в parent при виклику компоненти передаємо цій змінні значення.

Наприклад:
Parent.html

<template>
    <div class="container slds-p-around_large slds-theme_shade">
        <div class="slds-float_right">
            <c-child parent-data={data}></c-child>
        </div>
    </div>
</template>

Child.js

import { LightningElement, api } from 'lwc';
 
export default class SupportManager extends LightningElement {
    @api parentData;
}

Тепер уявімо ситуацію, де в нас є така ієрархія компонентів.
image
Рис. 1 Ієрархія компонентів

Як отримати дані з компоненти child2 у child1? Передати з child2 до parent, а потім з parent до child1? Такий варіант можливий, хоч і далеко не найкращий. Проте можуть бути випадки, коли parent компоненти не буде. Що у такому випадку робити?

image
Рис. 2 Схема непов’язаних компонент

На допомогу приходить Lightning Message Service, який дозволяє нам комунікувати між компонентами, навіть, якщо вони непов’язані.

Для того, щоб використовувати Lightning Message Service у своєму проєкті, насамперед потрібно створити Lightning Message Channel. Для цього переходимо до папки default (force-app->main->default) і створюємо нову папку з назвою “messageChannels”. У ній створюємо файл MessageChannel.messageChannel-meta.xml
Вставляємо код:

<?xml version="1.0" encoding="UTF-8"?>
<LightningMessageChannel xmlns="http://soap.sforce.com/2006/04/metadata">
    <masterLabel>MessageChannel</masterLabel>    
    <description>This is a sample Lightning Message Channel for the article which describe LMS
	</description>
    <isExposed>true</isExposed>
    <lightningMessageFields>
        <description>This is the boolean value that will be transmitted</description>
        <fieldName>isChangedWithoutSave</fieldName>
    </lightningMessageFields>
</LightningMessageChannel>

У цьому файли ми описуємо метадату каналу. На 3 рядку вказуємо masterLabel – це назва каналу, по ній пізніше будемо імпортувати канал у компоненти. На 6–9 рядках lightningMessageFields – це поле, що описує, які дані ми будемо передавати через канал. Воно не є обов’язковим, більше того, канал буде працювати навіть якщо ми передамо поля, які не відповідають цьому опису. Проте рекомендується описувати, які саме поля будуть передаватись через канал, і дотримуватись опису каналу при передачі даних. Це спростить розуміння і зекономить час іншому розробнику, який буде працювати з вашим message channel.

Далі створимо дві компоненти LWC, які будуть непов’язані між собою.
1: Profile
Profile.html

<template>
    <div class="container slds-p-around_large slds-theme_shade">
        <div>
            <lightning-icon icon-name="standard:individual"></lightning-icon>
        </div>
        <div>
            <lightning-input onkeyup={handleKeyUp} name="first-name" label="First Name"></lightning-input>
            <lightning-input onkeyup={handleKeyUp} name="last-name" label="Last Name"></lightning-input>
            <lightning-input onkeyup={handleKeyUp} name="email" label="Email"></lightning-input>
        </div>
        <div class="slds-p-top_medium slds-align_absolute-center">
            <lightning-button label="Save" onclick={save}></lightning-button>
        </div>
    </div>
</template>

Profile.js

import { LightningElement, wire } from 'lwc';
import { publish, MessageContext } from 'lightning/messageService';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import MessageChannel from '@salesforce/messageChannel/MessageChannel__c';
 
export default class Profile extends LightningElement {
 
    @wire(MessageContext)
    messageContext;
   
    handleKeyUp() {
        const payload = {
            isChangedWithoutSave: true,
        };
        publish(this.messageContext, MessageChannel, payload);
    }
 
    save(){
        const payload = {
            isChangedWithoutSave: false,
        };
        publish(this.messageContext, MessageChannel, payload);
 
        const event = new ShowToastEvent({
            title: 'Data saved',
            message: 'The data has been successfully saved, so you can safely exit the page',
            variant: 'SUCCESS'
        });
        this.dispatchEvent(event);
    }
 
}

Компонента profile є publisher в даному випадку, тобто в методах handleKeyUp і save публікується повідомлення Lightning Message Service. Викликається метод publish, який приймає три параметра:

  1. messageContext – об’єкт MessageContext надає інформацію про компонент, який використовує Lightning Message Service. Отримати цей об’єкт можна через декоратор wire MessageContext, як показано на рядку 8–9.
  2. MessageChannel – кастомний канал, який ми створили раніше. Щоб імпортувати канал використовуйте модуль ‘@salesforce/messageChannel/…’ і замість ‘…’ впишіть masterLabel каналу з суфіксом ‘__c’. Наприклад в нашому випадку це буде – import MessageChannel from ‘@salesforce/messageChannel/MessageChannel__c’;
  3. Payload – серіалізований об’єкт JSON в якому передаються дані. Повідомлення не може передавати функції.

2: Header
Header.html

<template>
    <div class="container slds-p-around_large slds-theme_shade">
        <div style="justify-content: flex-end" class="slds-page-header__row">
            <div class="slds-float_right">
                <lightning-button onclick={exit} label="Exit to main page"></lightning-button>
            </div>
        </div>
    </div>
</template>

Header.js

import { LightningElement, wire, track } from 'lwc';
import { subscribe, MessageContext } from 'lightning/messageService';
import MessageChannel from '@salesforce/messageChannel/MessageChannel__c';
import LightningConfirm from 'lightning/confirm';
 
const URL_TO_REDIRECT = '/lightning/setup/SetupOneHome/home';
 
export default class Header extends LightningElement {
 
    @track subscription = null;
    @track isSafeExit;
   
    @wire(MessageContext)
    messageContext;
   
    connectedCallback(){
        this.subscribeToMessageChannel();
    }
 
    subscribeToMessageChannel() {
        this.subscription = subscribe(
            this.messageContext,
            MessageChannel,
            (message) => this.handleMessage(message)
        );
    }
 
    handleMessage(message) {
        this.isChangedWithoutSave = message.isChangedWithoutSave;
    }
 
    exit(){
        if(this.isChangedWithoutSave){
            const redirect = LightningConfirm.open({
                label: 'Attention!',
                message: 'Your changes will be discarded. Are you sure?',
                theme: 'warning'
            });
            redirect.then(result => {
                if(result){
                    window.location.href = URL_TO_REDIRECT;
                }
            });
        } else {
            window.location.href = URL_TO_REDIRECT;
        }
    }
}

У компоненті header в методі connectedCallback викликається метод subscribeToMessageChannel, який підписується на канал і методом handleMessage буде обробляти вхідні повідомлення. connectedCallback – це стандартний метод, який викликається в момент, коли компонента вставляється в DOM дерево, тобто на момент коли компонента згенерується, вона вже буде підписана на канал, так як цей метод викликається перший. track subscription – це приватна змінна, яка відповідає за підписку на message channel.

Коли в компоненті profile вводяться дані в поля, кожен раз публікується повідомлення до компоненти header, в якому передається логічна змінна, що дані не збережені. При нажаті на кнопку “Save” публікується повідомлення до компоненти header, в якому передається логічна змінна, яка означає, що дані успішно збережені. Самого процесу збереження не реалізовано, так як моя ціль показати як працює саме Lightning Message Service.
Компонента header в залежності від змінної isChangedWithoutSave або дозволить нам перейти на головну сторінку, або ж покаже повідомлення:”Ваші дані не будуть збережені”.
Зверніть увагу, що ми передаємо дані між компонента, які ніяк не зв’язані.


Рис. 3 Приклад роботи компонент

Наприкінці хотілось би додати декілька слів про переваги та недоліки LMS. Почнемо з переваг.

У випадку, якщо ви маєте дві незв’язні компоненти, то реалізувати комунікацію між ними на клієнті можна лише за допомогою LMS. Наступною перевагою є те, що можна комунікувати між Visualforce, Aura і LWC на одній LWC сторінці. Також він досить зручний, якщо є дані у child компоненті, які потрібно передати на декілька рівнів вверх, то в такому випадку буде незручно використовувати dispatchEvent в декількох компонентах. У інших випадках я рекомендую вам використовувати dispatchEvent для Child-to-Parent, або анотацію api для передачі даних Parent-to-Child.

До недоліків можна віднести той факт, що Lightning Message Service збільшує навантаження на систему, в порівнянні з dispatchEvent-ом чи декоратором api, тому його недоцільно використовувати всюди.

На цьому все, якщо у вас залишились або з’явились питання по темі, то пишіть у коментарях під цією статтею.

7 Likes

Спасибо большое!!! Как раз искал это для своего проекта. Получилось сделать всё с ервого раза!

очень хороший мануал

2 Likes

thanks my issue has been fixed.