Week 14: Widget Communication: $broadcast, $emit & Shared Services

Two widgets sitting in different containers on the same page can't see each other's c directly — they need a shared channel. AngularJS gives you two: events broadcast through $rootScope, and shared services injected into multiple controllers. Knowing which one fits a given situation is the actual skill here.

Module 14 of 25 Week 14 of 26 ~3–4 Hours Hands-on Exercise Included

By the end of this week, you'll be able to

  • Send and receive events between widgets using $rootScope.$broadcast/$emit/$on
  • Share state across widgets using an injectable AngularJS service
  • Choose events vs. shared state based on the actual communication pattern needed

1. Cross-Widget Events with $rootScope

Every widget's controller shares the same page-level $rootScope, which makes it a natural event bus: one widget broadcasts an event downward (or emits it upward) through the scope tree, and any other widget listening with $on receives it — no direct reference between the two widgets required.

Widget A — Client Controller (the filter widget)
function($rootScope) {
  var c = this;
  c.selectedGroup = '';

  c.applyFilter = function() {
    $rootScope.$broadcast('incident-filter-changed', {
      group: c.selectedGroup
    });
  };
}
Widget B — Client Controller (the list widget, elsewhere on the page)
function($rootScope, $scope) {
  var c = this;

  $scope.$on('incident-filter-changed', function(event, payload) {
    c.data.action = 'filter';
    c.data.group = payload.group;
    c.server.update();
  });
}

This is deliberately similar to an Angular EventEmitter paired with a shared service's Subject — the mechanics differ, but the shape of the problem it solves (decoupled sibling components reacting to each other) is the same one you've solved before.

2. Shared AngularJS Services

For state that needs to persist and be read by multiple widgets — not just a one-off notification — an injectable AngularJS service (defined once, in a dedicated "Angular Provider" record) behaves like a singleton, exactly like an Angular service provided at the root injector.

Angular Provider — dashboardStateService
function() {
  var selectedGroup = null;

  return {
    getSelectedGroup: function() {
      return selectedGroup;
    },
    setSelectedGroup: function(group) {
      selectedGroup = group;
    }
  };
}
Any widget's Client Controller — injecting the shared service
function(dashboardStateService) {
  var c = this;
  c.selectGroup = function(group) {
    dashboardStateService.setSelectedGroup(group);
  };
}
Injected by name, same as everywhere else in AngularJS

The service becomes available to any widget controller simply by naming it as a function parameter — AngularJS's dependency injection resolves it by matching the parameter name to a registered provider, the same mechanism that hands you spUtil or $timeout without you wiring anything up explicitly.

3. Choosing Events vs. Shared State

A rough rule that holds up well in practice: reach for events when you're modeling something that happened at a point in time (a filter was applied, a record was saved) that other widgets should react to once. Reach for a shared service when you're modeling state that needs to be read by multiple widgets, including ones that mount after the state was last set — an event fired before a widget existed is simply missed, while a shared service's current value is always readable on demand.

4. Hands-on Exercise

Hands-on

Build a two-widget filter/list pair

Requirements:

  1. Build the filter widget and list widget shown above, place both on the same portal page, and confirm selecting a group in the filter widget updates the list widget via the broadcast event.
  2. Now add a shared service holding the currently selected group, updated whenever the filter changes.
  3. Add a third widget — a small "Currently Filtering By" label — that reads the shared service's value on load (not via an event) so it shows the correct group even if it's added to the page after a filter selection was already made.
  4. Explain, in a code comment, why the third widget uses the shared service instead of listening for the broadcast event.

5. Knowledge Check

Four quick questions. Expand each to check your answer.

Q1

How can two widgets in different containers on the same page communicate without a direct reference to each other?

Every widget controller shares the same page-level $rootScope. One widget broadcasts or emits an event through it with $rootScope.$broadcast()/$emit(), and any other widget listening with $scope.$on() for that event name receives it, with no direct reference between the two widgets needed.

Q2

What plays the role of an Angular EventEmitter/shared service pairing in the Service Portal widget world?

$rootScope.$broadcast()/$emit()/$on() for one-off event notifications, and an injectable AngularJS service (registered as an Angular Provider record) for shared, persistent state — the same two tools, conceptually, that solve decoupled cross-component communication in modern Angular, just via different specific APIs.

Q3

Why might a widget miss a broadcast event that another widget already fired?

Events are only received by widgets that are already listening at the moment the event fires. A widget that mounts after the event was broadcast — for example, one added to the page later, or one whose container renders after the event fired — simply never sees it, since there's no replay or buffering of past events.

Q4

When should you reach for a shared service instead of a broadcast event?

When you're modeling persistent state that multiple widgets need to read on demand, including widgets that might mount after the state was last changed — a shared service's current value is always readable, unlike an event which is only received by listeners active at the moment it fires.