I posted the same question in Lightning Component group in the partner community and got the below response from the salesforce employee:
In a LEX app that uses the console layout, each page's contents are maintained in the DOM and in memory at the same time, making it possible to switch between workspace tabs by merely updating CSS instead of recreating components and rerendering them.
In a LEX app that uses the standard layout container, each page's contents are also maintained in the DOM and in memory, but the rendered markup of components from pages that the user previously visited is hidden via CSS. This is all managed by a special layout component that maintains a set of components keyed by the page identifier (i.e. its URL). This layout allows the application to handle back/forward traversal through the browser history by merely changing CSS. In some cases, it also helps maintain scroll position, but that's not guaranteed or officially supported presently.
In both layouts, for instance, it is possible that a component will be instantiated twice, once for 2 distinct record pages, each for a different record. Developers have typically worked around this scoping issue where their components on other pages, which are currently hidden, will receive an event fired by a component on the current page and which is intended for other components on that same page.
The workaround usually involves adding a piece of data to the event
payload that is unique to that page, and recordId is typically what's
used. Handlers may then check that parameter when they receive the
event and compare it against their recordId to see if it matches
before actually doing work.
There is currently no platform-supported mechanism that builds in the concept of a page-scoped event explicitly. That may change in the future, but adding information to the event that enables the components to scope it themselves will often suffice.
In either case, adding code to a component that manually destroys itself is not a good idea and will often lead to problems.
If you assign the recordId via markup (e.g. in the parent component <c.childCmp recordId="{!v.recordId}"/>
then it is important to stash a copy of the record id in another attribute in the child. This is because the LEX system binds the recordId value in the parent to the child and when the parent's recordId is changed so will the children. But if you create <aura:attribute name="myCaseId" type="String" />
and copy the incoming record id into this myCaseId property, in the init handler, then you will have a safe copy of the record id to use when all the events come along.
Use Streaming API to capture the events from the database.
Create a push topic
First, you need to create a push topic with a specific query, based on that query the event will be fired.
In your case, it will be something like.
Select Name,..., LastModifiedById FROM Case where Status = 'Closed'
As you need to fire this event only when the case is updated you can set NotifyForOperationUpdate
to true on Push Topic record.
Make sure that the IsActive
is set to true.
So the final code to insert the push topic will be like below.
PushTopic pushTopic = new PushTopic();
pushTopic.Name = 'WhenCaseClosed';
pushTopic.Query = 'Select Name,..., LastModifiedById FROM Case where Status = \'Closed\'';
pushTopic.ApiVersion = 49.0;
pushTopic.NotifyForOperationUpdate = true;
pushTopic.NotifyForFields = 'Where'; // as you want to fire this event only when the status field is changed.
insert pushTopic;
Note that you just need a single push topic record in the org.
Use the Streaming API event listener to open the utility.
Add the lightning:empApi
in your component and add the event handler in the init handler of the component. Note you will also need lightning:utilityBarAPI
.
<lightning:empApi aura:id="empApi" />
<lightning:utilityBarAPI aura:id="utilitybar" />
Now let's see the handler code.
const empApi = component.find('empApi');
const replayId = -1;
const channel = '/topic/WhenCaseClosed'; // note the same name we have set to the push topic.
// Subscribe to an event
empApi
.subscribe(
channel,
replayId,
$A.getCallback(eventReceived => {
// Process event (this is called each time we receive an event)
if(eventReceived.data && eventReceived.data.sobject){
// check if the last modifyby id of the current record is same as the logged in user.
if(eventReceived.data.sobject.LastModifiedById === $A.get("$SObjectType.CurrentUser.Id")){
// open the utility here.
}
}
})
)
.then(subscription => {});
As we had already added the LastModifiedById
in the query, so we are checking if the LastModifiedById
is the same as the logged-in user. So we can say that it is the same user who has closed the case.
I hope this will solve all of your problems.
Corner case.
There is a possibility that the LastModifiedById
might get changed in between the case is closed and the event is received on the Utility component. To avoid that you can create a custom field to store the user id who has closed the case.
The Platform events
I have not tried but I think this can be also achieved with Platform Events as they also use Streaming Apis and EMP APIs. One advantage using PushTopic over Platform events I see is you don't need to fire the any events from the trigger like we do in Platform Events.
Best Answer
My POC shows that this is indeed true, probably by design (I guess it makes sense, sort of).
Lightning-console apps consider every LOADED tab in scope of an application event. To further complicate this behavior, if more than one record tab is LOADED, then you refresh the browser, only the current record tab is back in scope of an application event. I think this is because the second tab hasn't bootstrapped into the one.app namespace yet.
FYI, I'm using my own service component framework (https://github.com/tsalb/sfdc-lightning-service-components) to fire these events, but the underlying application event firing mechanism is the same, I just have method wrappers to help fire generic ones.
Place both on same Lightning Record Page in Lightning-console app
AppEventFire.cmp
AppEventFireController.js
AppEventListener.cmp
AppEventListenerController.js
Then open up two records within the same Lightning-console app, fully load both (so that the fire.cmp and listener.cmp exist, are rendered). You'll see that if you fully load two Lightning-console app tabs, then click the button from either one, both will receive it. I'm writing to server to verify that these application events persist and it's not just a trick of browser / component memory.
P.S. I've added what I'm calling a Record Event to my framework which deals with this very specific issue, very specifically inside lightning-console apps. I'll update my framework repo when I get some time.