This lesson is for PRO members.

Unlock this lesson NOW!
Already subscribed? sign in

Communicate State Changes in Angular with an Event Bus

2:49 Angular 1.x lesson by

In Angular 2, we use observables to push new data to our components. In Angular 1, we occasionally need to notify a component that a collection has changed so that we can perform an operation at the component level.

For example, when we are editing or creating a bookmark, and we navigate to a new category, we are left in this awkward state of an incomplete edit.

We need to be notified when a new category is selected at the bookmark level so that we can reset the current bookmark. Even though we do not have the convenience of an observable to emit state changes to us, we can still spin up an event bus to let us know when we need to hit reset.

Get the Code Now
click to level up

egghead.io comment guidelines

Avatar
egghead.io

In Angular 2, we use observables to push new data to our components. In Angular 1, we occasionally need to notify a component that a collection has changed so that we can perform an operation at the component level.

For example, when we are editing or creating a bookmark, and we navigate to a new category, we are left in this awkward state of an incomplete edit.

We need to be notified when a new category is selected at the bookmark level so that we can reset the current bookmark. Even though we do not have the convenience of an observable to emit state changes to us, we can still spin up an event bus to let us know when we need to hit reset.

Avatar
Przemysław

Does using rootscope as event bus is a good idea? Can this make a performance problemu? Broadcast is not a sample piple, mostly beacause It visit all scopes. It is not a problem in old aproach when you used mostly controllers and directives but know when everything is a component with own isolated scope. Or It is a problem but only in my head ;)

In reply to egghead.io
Avatar
Fabio Bedini

I agree with you,
I spent my money to learn all
best practice but this is not
the case :(

In reply to Przemysław
Avatar
Lukas

Hi Przemysław --
This is not a performance problem for a couple reasons...

First, state change is ALREADY being communicated via $scope through isolated scope bindings and optimized for it. Where this technique is necessary is when you need to communicate a state change that is NOT on $scope, i.e. model data. How do you tell the application that something has changed in service? How do you do this without coupling classes together? This is why the observer pattern exists and essentially what we are implementing.

Secondly, we are only broadcasting to container components so that they can update their presentation state which then gets propagated to the presentation components via isolated scope. So you are in part correct, this pattern is used to communicate JUST ENOUGH so that parallel container components can make the necessary adjustments and let component $scope take over.

Thirdly, this is a similar pattern to how Redux works in that instead of $rootScope.$broadcast, we have store.dispatch and instead of $scope.$on we have store.select and observables handle the eventing for us.

Fourthly, there are specific cases for using an event bus but $rootScope is the single BEST mechanism for the job. It has already been optimized to effectively communicate with every component within the application. How (or why) could we possibly roll something more efficient on our own?

Oh... one more... in this specific case... the frequency in which we are broadcasting is negligible in terms of performance considerations. If we were hooked up to a web socket that was broadcasting 100 times a second then I may reconsider my solution for that case.

In reply to Przemysław
Avatar
Lukas

Hi, Fabio - please see my answer above. If you know of a better way to communicate state mutations to parallel components that does not include tightly coupling classes together and involves a mechanism more efficient than $rootScope, I would be happy to consider it

In reply to Fabio Bedini
Avatar
Kate

how would you implement the navigation like in the original bookmark app here with uiRouter?

Avatar
Lukas

Hey Kate,

This particular implementation of an event bus would not be necessary with eggly’s implementation of uiRouter.

As an example in the original app, if a user starts editing a bookmark and then selects a category, the controller/view pair associated with the current state is unloaded and whatever pair is associated with eggly.categories.bookmarks then gets loaded in, effectively clearing whatever edit (or create) is happening.

In reply to Kate
Avatar
Kate

Thx Lukas that makes sense but how would you go about and implement the navigation with uiRouter on this component structure ... I have tried different ways but I can't get it to work. Thx

In reply to Lukas

In Angular 2, we use observables to push new data to our components. In Angular 1, we occasionally need to notify a component that a collection has changed so that we can perform an operation at the component level.

For example, when we are editing or creating a bookmark, if we navigate to a new category mid-stride, we're left in this awkward state of an incomplete edit that is not congruent with the view that we are looking at. What needs to happen is we need to be notified when a new category is selected at the bookmark level so that we can reset the current bookmark.

Even though we do not have the convenience of an observable to admit state changes to us, we can still sped up our own event bus to let us know when we need to hit reset. We're going to hop into the categories model and inject $rootScope.

Just a bit of cautionary advice, be very judicious about what you put on $rootScope. It is not a global grabbag of properties, but it works very well as an event bus as all events come from the scope object and $rootScope being the mothership of them all.

model/categories.model.js

class CategoriesModel {
  constructor($q, $rootScope) {
    'ngInject';

    this.$q = $q;
    this.$rootScope = $rootScope;
    this.currentCategory = null;
    this.categories = [ ... ];
  }

Within our setCurrentCategory method, when we have set a new category, we're going to call this $rootScope.$broadcast(), and we're just going to emit an event called onCurrentCategoryUpdated.

model/categories.model.js

setCurrentCategory(category) {
  this.currentCategory = category;
  this.$rootScope.$broadcast('onCurrentCategoryUpdated');
}

Then from here, let's hop into the bookmark controller. We're going to inject $scope, and we're going to listen for that event. $rootScope is broadcasting the event, so it goes from the $rootScope component all the way down to all the children components, so we're guaranteed using $broadcast on $rootscope that every scope object is going to hear, will be notified of that event. We can go this scope on, onCurrentCategoryUpdated.

bookmarks/bookmarks.controller.js

class BookmarksController {
  constructor($scope, CategoriesModel, BookmarksModel) {
    'ngInject';

    this.$scope = $scope;
    this.CategoriesModel = CategoriesModel;
    this.BookmarksModel = BookmarksModel;
  }

  $onInit() {
    this.BookmarksModel.getBookmarks()
      .then((bookmarks) => {
        this.bookmarks = bookmarks;
      });

    this.$scope.$on('onCurrentCategoryUpdated', this.reset.bind(this));
    this.getCurrentCategory = this.CategoriesModel.getCurrentCategory.bind(this.CategoriesModel);
    this.deleteBookmark = this.BookmarksModel.deleteBookmark;
  }

  initNewbookmark() { ... }

We're going to call this.reset, but we need to bind(this) method to the BookmarksController itself because we will lose that if we do not add bind(this). Let's hop into the browser, and now you can see that if we're going to create or edit state, that when we select a new component or a new category, it is reset.

Using the $rootScope.$broadcast() in our model, we can notify our BookmarksController that a category was selected, and then allow the BookmarksController to perform its logic at the component level. This is how you notify your components that a collection has changed at the model.

HEY, QUICK QUESTION!
Joel's Head
Why are we asking?