Communicating between Service Portal widgets: understanding $emit and $broadcast - jcmings/sn GitHub Wiki

Widgets. Everyone's favorite topic. Widgets are a super important part of the platform but aren't the easiest to understand. Today, we'll be explaining how we can communicate between two widgets on the same page. There are innumerable use cases for this. Off the top of my head, I can think of a few:

  • Automatically updating a table after submitting a form
  • Dynamically changing what data we see about a user by checking different boxes or selecting from dropdowns
  • Displaying visuals or messages to improve user-experience

With all that said, you can expect to gain an understanding of how to communicate between widgets from this post. I'll provide an example with source code at the bottom.

Nathan Firth has a great post explaining this topic on his website. I’ll largely be explaining the same topics in this post. I highly recommend using his docs are a supplement to mine. After all, how do you think I found most of this out?

Sending data: $broadcast, $emit, $scope and $rootScope

Before we begin, it’s important to understand the difference between $broadcast and $emit, and $scope and $rootScope. image

As depicted in the image on the left, if you’re sending data from parent to child, you use the $broadcast method.

If you’re sending data from child to parent, you use the $emit method. An easy way to remember this is $emit sends up. (Editing this post months later to say that I totally forgot this "catchphrase" and had to come back to look it up.)

Since one widget is contained within another, you use $scope.

As illustrated in the image on the right, if you’re sending data from Widget A to Widget B (or vice versa), you’ll use $rootScope.$broadcast. Since the widgets are independent, we use $rootScope to pass the data “globally,” as opposed to within the same scope like in the parent/child example.

Receiving data: $on

image

The receiving widget will always use the $on method (following the same $scope/$rootScope usage as the sending widget).

A real-world example, with source code

For this example, I have two input boxes in one widget, and a table in the other. I want to send the data I enter in the input boxes to my table. We won't be doing any database inserts or updates in this example; it's strictly client-side. (However, I'll note that you could modify the script to insert records to the database fairly easily. Bonus points if you also load in data from the database.)

Here's the communication in action: Widgets4

And here's how it works:

In our first widget, on the left, which I'll call Widget One, we're capturing the user's inputs in an object we created called infoObject (this will all make more sense when we take a look at the code under the hood). infoObject has two properties: first_name and last_name. When we click the button Send Over Info, we send over the infoObject to the widget on the right ("Widget Two").

Widget Two receives the infoObject with the first_name and last_name properties and pushes the data into an array. The HTML of the table is set up to display every object in the array by using an ng-repeat. So for each object we send over, we'll see that display in the table.

Check out the code for each of the widgets below.

Widget One code

HTML:

  • We use ng-model to define our input boxes and be able to access them in the client controller
  • We call the sendOverInfo() function using an ng-click of the button
<div class="panel panel-default panel-body m-t m-b">
  <h1>This is the sending widget </h1>
  <p>We'll $broadcast our data on a button click.</p> 
  <div class="row">
    <div class="col-md-6">
      <input type='text' class='form-control' ng-model='firstNameBox' placeholder='First name goes here'>
    </div>
    <div class="col-md-6">
      <input type='text' class='form-control' ng-model='lastNameBox' placeholder='Last name goes here'>
    </div>
  </div>  
  <br> <!-- Don't judge me -->
  <button class="btn btn-primary pull-right" ng-click="sendOverInfo()">Send Over Info</button>
</div>

Client controller -- note how we are passing both $scope and $rootScope into the parameters at the top:

  • In our sendOverInfo() function, we capture the inputs of our ng-model by using using $scope and the ng-model's name (firstNameBox or lastNameBox) and store them as properties in the infoObject object
  • Then, we broadcast our data using $rootScope.$broadcast, the name of our event (sendingInfo -- it can be whatever you want), and the object we're passing through (infoObject)
  • After that, we clear the input boxes to allow for new entries
  • We also console.log a success message for testing purposes
api.controller=function($scope, $rootScope) {

	$scope.sendOverInfo = function() {
		//Console log a success message
		console.log('Send success!');
		
		//Populate the infoObject with the data from our input boxes
		var infoObject = {
			first_name: $scope.firstNameBox,
			last_name: $scope.lastNameBox
		}		
		//Broadcast our data
		$rootScope.$broadcast('sendingInfo', infoObject)
		
		//Clear the input box values after the data has been sent
		$scope.firstNameBox = '';
		$scope.lastNameBox = '';
	}	
};

My server script and CSS are untouched and empty.

Widget Two code

HTML:

  • We have our table which is set up to ng-repeat each item in our array
  • We access the items in our array using {{item.first}} or {{item.last}}
<div class="panel panel-default panel-body m-t m-b">
  <h1>This is the receiving widget </h1>
  <p>We'll receive the data sent (using $on) and add it to the table below.</p>

<table>
  <thead>
    <tr>
      <th>First Name</th>
      <th>Last Name</th>
    </tr>
  </thead>
  <tbody>
    <tr ng-repeat="item in array">
      <td>{{item.first}}</td>
      <td>{{item.last}}</td>
    </tr>
  </tbody>
</table>
</div>

Client controller -- noting the same usage of $scope and $rootScope here as well:

  • Here, we are listening for the broadcast of the sendingInfo event by using $rootScope.$on -- noting that the event name has to match the event we broadcasted in Widget One
  • When that event gets received, we push infoObject.first_name and infoObject.last_name into our array with the properties first and last
  • Note that we declare our array outside of the receiver so it doesn't reset every time we receive data
  • We're also console.loging a success message for testing at the top of our receiver function
api.controller=function($scope, $rootScope) {

	//Create our array
	$scope.array = [];
	
	//Receive the data: when 'sendingInfo' happens, a function that passes infoObject is called
  $rootScope.$on('sendingInfo', function(event, infoObject) {
		
		//Console log a success message
		console.log('Data received!');
		
		//Push the data int our array
		$scope.array.push(
			{first: infoObject.first_name, last: infoObject.last_name});
  });

};

And lastly, CSS for our table:

  • Just some styling for our table to make it look a little cleaner... thanks ChatGPT!
table {
  width: 100%; /* Set the width of the table to fill its parent element */
  border-collapse: collapse; /* Collapse borders so they don't double up */
}

table, th, td {
  border: 1px solid black; /* Set the border color for the table, th, and td elements */
}

th, td {
  padding: 10px; /* Add some padding around the content in table headers and cells */
  text-align: left; /* Align the text to the left */
}

th {
  background-color: #f2f2f2; /* Set a background color for the table headers */
}

Hopefully this makes understanding widget communication a little bit easier. ChatGPT was a great help to me in explaining how this all works in order for me to write this guide. If you're still struggling, I'd recommend asking Chat to ELI5 how it all works.

⚠️ **GitHub.com Fallback** ⚠️