Change Data Capture (CDC) use with Lightning Web Component
With Change Data Capture, you can receive changes of Salesforce records in real time and synchronize corresponding records in an external data store. Change Data Capture publishes events for changes in Salesforce records corresponding to create, update, delete, and undelete operations.
When to Use Change Data Capture:-
- Receive notifications of Salesforce record changes, including create, update, delete, and undelete operations.
- Capture changes of most fields for all records.
- Get information about the change in the event header, such as the origin of the change, so you can ignore changes that your client generates.
- Perform data updates using transaction boundaries when more than one operation is part of the same transaction.
Use Case:-
- Suppose you want to see confirmation message on Account Record page when phone number is update by back end or external API without refresh entire record page.
- Suppose one opportunity is assigned to opportunity team members and you are also working on this on same time with another team members. if opportunity stage changed by Team member then this update stage will reflect on your side also without refresh record page.
For these use cases we need to use Change Data Capture.
All custom objects and few Standard Objects like Account, Contact, Lead, User, Order, OrderItem, Product2, and others supports CDC.
Step 1:- We need to enabled Change Data Capture as below screen:
Step 2:- We need to create LWC component as below
changeDataCaptuerComponent.thml
<template>
</template>
changeDataCaptuerComponent.js
import { LightningElement,api } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent';
import { subscribe, unsubscribe, onError} from 'lightning/empApi';
import { getRecordNotifyChange } from 'lightning/uiRecordApi';
export default class ChangeDataCaptuerComponent extends LightningElement {
@api recordId;
channelName = '/data/AccountChangeEvent';
subscription = {}; //subscription information
responseMessage; //message to be shown at UI
isDisplayMsg; //indicator for message to be displayed
// Initializes the component
connectedCallback() {
this.handleSubscribe();
// Register error listener
this.registerErrorListener();
}
// Handles subscribing
handleSubscribe() {
// Callback invoked whenever a new event message is received
const messageCallback = (response) => {
console.log('Update message received: ', JSON.stringify(response));
// Response contains the payload of the new message received
this.handleNotification(response);
};
// Invoke subscribe method of empApi. Pass reference to messageCallback
subscribe(this.channelName, -1, messageCallback).then(response => {
// Response contains the subscription information on subscribe call
this.subscription = response;
this.handleNotification(response); // this method use for refresh only particular filed value instead all entire page
});
this.handleUnsubscribe();// just unsucribe after complete action
}
// Handles unsubscribing
handleUnsubscribe() {
// Invoke unsubscribe method of empApi
unsubscribe(this.subscription, response => {
console.log('unsubscribe() response: ', JSON.stringify(response));
// Response is true for successful unsubscribe
});
}
registerErrorListener() {
// Invoke onError empApi method
onError(error => {
console.log('Received error from server: ', JSON.stringify(error));
// Error contains the server-side error
});
}
//this method checks if current record got updated and shows message on UI
handleNotification(response){
if(response.hasOwnProperty('data')){
let jsonObj = response.data;
if(jsonObj.hasOwnProperty('payload')){
let payload = response.data.payload;
let recordIds = payload.ChangeEventHeader.recordIds;
//Here you can add criteria for particular action like INSERT, UPDATE and DELETE with particuler Object
//if(payload.ChangeEventHeader.entityName === 'Account')
//if(payload.ChangeEventHeader.changeType === 'UPDATE')
//if(payload.ChangeEventHeader.changedFields.includes('Phone') === 'UPDATE')
//find the current recordId in the array and if found then display message
const recId = recordIds.find(element=> element == this.recordId);
if(recId !=undefined){
this.handleRefresh();
}
}
}
}
//this method refreshes current record fields value instead of entire page
handleRefresh(){
getRecordNotifyChange([{recordId: this.recordId}]);
const event = new ShowToastEvent({
title: 'Success',
message: 'Account phone update successfully!!',
variant: 'success',
mode: 'dismissable'
});
this.dispatchEvent(event);
}
}
Step 3:- We need to put this component on Particular Lightning Record page where we want to get updated value. In this scenario we want to update Account phone number so we will put this component on Account Record Page as below:-
Step 4:- Just for testing open your developer console and update particular account phone by DML and after this operation check your account record page that’s already opened, now updated Phone Number will be reflect on record page.
After update operation you will get payload as below
Event payload structure:-
{
“data”:
{“schema”:”JPyUm_b7b4SSjXobT5DOPg”,
“payload”:
{“LastModifiedDate”:”2023-02-17T04:28:38Z”,”Phone”:”999999999″,
“ChangeEventHeader”:
{“commitNumber”:603217770054,”commitUser”:”0055h000007V4S3AAK”,”sequenceNumber”:1,”entityName”:”Account”,”changeType”:”UPDATE”,
“changedFields“:[“Phone“,”LastModifiedDate”],”changeOrigin”:”com/salesforce/api/rest/57.0″,”transactionKey”:”0000b8b9-3a08-ffa6-efe2-efefea45484f”,”commitTimestamp”:1676608118000,”recordIds”:[“0015h000015Ed44AAC”]
}
}
,”event”:{“replayId”:25397141}
},
“channel”:”/data/AccountChangeEvent”
}