Thursday, December 4, 2014

The Top 5 Mistakes AngularJS Developers Make Part 3: Overusing $broadcast and $emit

This is the third part in a five-part series that covers common AngularJS mistakes. To recap, the top five mistakes I see people make are:

  1. Heavy reliance on $scope (not using controller as) 
  2. Abusing $watch
  3. Overusing $broadcast and $emit
  4. Hacking the DOM
  5. Failing to Test

When I posted the first article, one comment suggested that using controller as is fine for smaller controllers but large, complex controllers with dependencies might not work out as well. You’ll get to see some dependencies in this article, and hopefully a new way to think about how to manage interdependencies.

Overusing $broadcast and $emit

One advantage of using $scope is the variety of functions that are available like being able to $watch the model. I explained why this isn’t the best idea nor approach, and in this post will tackle another set of features. Fundamentally, the concept of events and being able to have custom events is a good one. In AngularJS, custom events are supported through the $emit (bubble up) and $broadcast (trickle down) functions to publish the event.

In Angular, scopes are hierarchical. Therefore, it is possible to communicate to child scopes or listen to children using these functions. The simple $on function is used to register for or subscribe to an event. Let’s take a simple example. Again, I’m using something contrived to show how it works but hopefully you can extrapolate to more complicated scenarios like master/detail lists or even pages with a lot of information that need some mechanism to auto-refresh when one area is updated.

For this example, I have three separate controllers. One is responsible for handling selection of a gender. A separate controller handles showing a related label and therefore must be aware of changes, and yet another controller counts how many times the gender is changed.

The HTML5 looks like this:

<div ng-app="myApp">
    <div ng-controller="genderCtrl">
        <select ng-options="g as g for g in genders" 
                ng-model="selectedGender">        
        </select>
    </div>
    <div ng-controller="babyCtrl">
        It's a {{genderText}}!
    </div>
    <div ng-controller="watchCtrl">
        Changed {{watches}} times.
    </div>
</
div>

The gender controller exposes the list and handles the current selection. Because other controllers depend on this selection, it watches for a change and broadcasts an event whenever it changes. Some of you may recognize this antique device we used in the past to broadcast music.

boombox

A common pattern I see implemented is to broadcast from the root scope because it’s guaranteed to reach all child scopes.

function GenderController($scope, $rootScope) {

    $scope.genders = genders;
    $scope.selectedGender = genders[0];
    $scope.$watch('selectedGender', function () {
        $rootScope.$broadcast('genderChanged',
            $scope.selectedGender);
    });
}

The controller that exposes the label listens for the event and updates the text accordingly.

function BabyController($scope) {
    $scope.genderText = labels[0];
    $scope.$on('genderChanged',
        function (evt, newGender) {

        $scope.genderText =
            newGender ===
            genders[0] ? labels[0] : labels[1];
    });
}

Finally, a third controller listens for the event and updates a counter every time it is fired.

function WatchController($scope) {
    $scope.watches = 0;
    $scope.$on('genderChanged', function () {
        $scope.watches += 1;
    });
}

This application works (check it out here) but I think we can do better. Why don’t I like this approach? Here are a few reasons:

  • It creates a dependency on $scope that I’m not convinced is needed.
  • It further creates a dependency on $rootScope.
  • If I choose not to use $rootScope, then my controllers have to understand the $scope hierarchy and be able to $emit or $broadcast accordingly. Try testing that! 
  • To react to a change also requires a dependency on $scope.
  • I now have to understand how custom events work and use their convention correctly (notice, for example, the new value is in the second parameter, not the first).  
  • I’m now doing something the “Angular way” that I might be able to do with pure JavaScript. I’m pretty sure the closer to JavaScript I stay, the easier it will be to upgrade and migrate this code later on.

OK, so what’s the answer? One way to look at this is not as a sequence of events (i.e. user changes gender, which triggers a change, which triggers an event, which triggers a response) but instead look at the result. What really happens? The gender change really transitions the state of the model. The label state simply alternates based on the gender selection state, and the counter iterates. So, state change results in mutated model. Do I really need a message to communicate this?

Let me break this down a different way. First, when I think about something common across controllers, I immediately think of a service. Whether it is defined via a factory or service isn’t the point here (if you don’t know or understand the difference, read Understanding Providers, Services, and Factories in Angular) but rather that there is a common state shared across controllers. So, I create a gender service:

function GenderService() { }
 
angular.extend(GenderService.prototype, {
    getGenders: function () {
        return genders.slice(0);
    },
    getLabelForGender: function () {
        return this.selectedGender === genders[0] ?
            labels[0] : labels[1];
    },
    selectedGender: genders[0]
});

The service gives me a copy of the list of genders, allows me to get or set a selected gender and returns the label for the gender. All of the useful, common functionality is packaged in one component. Aside from the way I wired it up using Angular’s extend function, it is a pure POJO object I can test without depending on Angular.

Now I can create a controller that is also a POJO. It does rely on the gender service so it will use constructor injection. Although Angular will handle this for me in the app, I can easily create my own instance of the gender service or mock it and pass it in the constructor for testing and still not depend on Angular at all … or $scope … or $emit … or $broadcast.

function GenderController(genderService) {
    this.genders = genderService.getGenders();
    this.genderService = genderService;
} Object.defineProperty(GenderController.prototype,
    'selectedGender', {
        enumerable: true,
        configurable: false,
        get: function () {
            return this.genderService.selectedGender;
        },
        set: function (val) {
            this.genderService.selectedGender = val;
        }
    });

Notice our “controller” is really just an object that exposes the list of genders and has a property that proxies the selected gender through to the gender service. I could actually have just exposed the service and bound to it directly, but that in my opinion is too much information. My UI should just be concerned with the list and selection, not the underlying implementation of how it is managed. The end result now is that you can select a gender and the service will hold the selection. Notice it is not sending any messages, so how does the label controller handle changes? Take a look:

function BabyController(genderService) {
    this.genderService = genderService;
} Object.defineProperty(BabyController.prototype,
    'genderText', {
        enumerable: true,
        configurable: false,
        get: function () {
            return this.genderService.getLabelForGender();
        }
    });

See how simple that is? I can write a test that creates three POJOs (the service and controllers), mimics updating the selection on the selection controller and verify the gender label changes on the label controller. My UI simply binds to the property it is interested in (the genderText property) and doesn’t get muddied with any presentation or business logic going on “behind the scenes.” When the label property is referenced, it asks the service what the label should be and always returns the correct value based on the current selection.

By now you’ve probably guessed what the watcher controller looks like …

function WatchController(genderService) {

    this.genderService = genderService;
    this._watches = 0;
    this._lastSelection = genderService.selectedGender;
} Object.defineProperty(
    WatchController.prototype,
    'watches', {
        enumerable: true,
        configurable: false,
        get: function () {
            if (this.genderService.selectedGender !==
                this._lastSelection) {
                this._watches += 1;
                this._lastSelection =
                    this.genderService.selectedGender;
            }
            return this._watches;
        }
    });

It simply keeps track of watches internally, and whenever they are accessed externally checks to see if the selection changed. If you don’t like this type of mutation in the property getter, you can expose it as a method and data-bind to the result of the method call instead.

Now I have four POJOs with no Angular dependencies. I can test them to my heart’s content, I don’t need to spin up a $scope and I don’t even care how $emit or $broadcast are implemented. Take a look for yourself at the working example with no $emit or $broadcast.

There is one thing to call out. Some of you may recognize that this approach (specifically for the watch controller) does depend on Angular indirectly. The implementation of the watcher is based on accessing the watches count. Independent of Angular, you could manipulate the selection several times and the watcher would miss it unless you polled it each time. I know in Angular it will always be refreshed due to the digest cycle so in the Angular context it will work fine, but if I wanted something more “independent” I’d need to expose an event from the gender service and register from the watcher service to update the count even when it isn’t polled. In that case I’d probably implement my own event aggregator that doesn’t rely on $scope or $scope hierarchy to function.

The reason this works without watches or messages, by the way, comes back to the way the Angular $digest cycle works. When you mutate the model by changing the gender selection, Angular automatically checks the values of the other properties that are data-bound. This, of  course, calls through to the getter that was implemented and results in the correct value being returned and refreshed to the UI. Once again, we avoid extra watches.

Because watches and event handlers can mutate the model, Angular actually has to go back and revaluate expressions each time a watch function or event handler is called to make sure there are no further updates. In this model, the model itself is already refreshed so there is only one pass needed (you’ll see multiple passes because there are multiple bindings, but no extra passes due to watches).

This approach reduces dependencies and therefore the complexity of the code, makes it easier to test, and provides a performance benefit to boost. It really just involves thinking about things in terms of relationships and state rather than the idea of a “process” that “pushes” things. Let Angular handle all of that lifting and keep your design simple and straightforward. In a later post I’ll reference a more complex project that shows a fully working app using this principle with no dependencies on $scope inside controllers.

Although I’ve addressed some common scenarios related to data-binding, invariably you will run into situations that require you to manipulate the DOM. It may be something simple like refreshing the page title based on a context change, or something more complex like rendering SVG or setting the focus on a control based on a validation error. That often leads to the fourth mistake I see developers make, which is hacking the DOM directly, often from the controller itself!

In the next post I’ll share with you how to handle that using AngularJS. Please take some time to comment and share your thoughts with me, and if you feel I can provide further value please don’t be shy – the contact form on this page will email me directly so reach out and let me know how I can help!

Thanks,

signature[1]

21 comments:

  1. I am afraid I have nothing to tell you about, but I just would like to say "keep going" and thank you for sharing with us.

    ReplyDelete
    Replies
    1. Vincenzo, thank you so much! I appreciate your comment.

      Delete
    2. +1 to @Vincenzo comment : )

      @Jeremy, thank you for this series, one of the best i have read so far,

      Delete
  2. Very nice explained. I have some questions though:

    Why do you need angular.extend to create Ctrl? You want to avoid unnecessary dependencies, don't you?

    Any particular reason for using Object.defineProperty or is it just "matter of taste"? I personally don't like it (to verbose).

    And the last one: What would be the (valid) use case to implement own event aggregator in an angular application?

    Can't wait for the next post. Keep it going.

    ReplyDelete
    Replies
    1. angular.extend just makes the definitions cleaner and less verbose, but I mentioned you can just set on the prototype directly to completely remove the dependency.

      I like Object.defineProperty as the ECMAScript5 standard for creating those. It is verbose, but there are many ways to simplify it with boilerplate, etc.

      Any time you need to communicate in a de-coupled fashion outside of the digest loop is when I would see the event aggregator making sense. For example if you have a polling process that might trigger some alert outside of the Angular digest loop.

      Thanks for the kind words and feedback.

      Delete
    2. @SekibOmazic you can use Typescript or ES6 for nicer syntax(also the accessors are much nicer and shorter to write).
      I rewrote Jeremy's solution to es6 and you can take a look here: http://jsbin.com/suzugicuti/3/edit?html,js,output

      Delete
  3. Jeremy amazing article and series!
    I'm building my angular apps similar way like you've described + component pattern ftw. I think this is the best way to build current angular apps and have them ready for possible migration to new angular 2.0.
    thanks again and keep up with your great work.

    ReplyDelete
  4. I approve! A service is a great, focused alternative to Angular eventing (the "EventAggregator" pattern as we used to say).

    Another suggestion ... that follows from the early part of your critique ... is to wrap the $rootScope message thingy in an application MessageService with the API of your dreams. Now inject THAT instead of $scope or $rootScope into the controllers and services that need it.

    Doing this will also prepare you for Ng 2.0 because that's what they're doing: refactoring $scope into proper services.

    ReplyDelete
  5. Great article, but I think that when you go with this approach, there is something crucial to understand, using shared state via service breaks as soon as your state is part of a reusable component.

    For example, lets say that the example given in this article is part of an entity form which is a reusable component.
    Now what will happen if you want to display two different entity forms each showing a different entity at the same time.
    The two forms will start interacting between them via the singleton service which is not what we want.

    This is essentially what happened to us and introduced problems in our implementation.

    I would say that the rule of thumb should be that if you have simple non reusable component, than it's ok to use this paroch with in the bounds of that component.

    but if you suspect that you would need to reuse that component or show multiple instances of that component at the same time, than this paroch should be avoided,

    Instead I would recommend to use angular events, I think they are the right tool for this job, but if you still want to avoid them a valid solution would be to compose your component of directives, and than use directive dependencies to inject a shared controller which than will be used similarly to create a shared state.

    ReplyDelete
    Replies
    1. I get what you're saying but also think we're on the same page. I'm suggesting this approach to communicate global state. You are talking about two instances, so it's not global state - you could create a local instance and bind to that. If they need to communicate, that implies there is a "common" element they share and it is that element that should be factored into a service, if that makes sense and I understand you correctly. Always tough to talk in abstracts, specific examples help to illustrate it better.

      Delete
    2. Indeed we are on the same page, I just felt the need to present other aspects of this issue as I have seen in the past people attempting to apply this pattern to reusable components, not seen immediately why this approach won't work for them.

      And even worst attempting to mitigate their folly by attempting to still use angular services by switching from services that manages global state to a service that is a manager of global states, which either resulted in memory leaks (as they subscribed but never unsubscribed from this global manager) or having a whole subscribe and unsubscribe dance in their controllers which again was dependent on scope and events, but in this case on $destroy event to unregister.

      In the end in most of those cases, just using angular events would have made the solution cleaner and simpler

      Another thing to consider here is that when you are writing a complex or an enterprise level app, you never know when your component will be elevated from been used in single location to been reused or have multiple instances, so it can be easier to just build it from the start as totally self encapsulated component, rather than going with global state. In most cases the difference between the approaches is only in the way you are injected with the shared state

      Delete
  6. I enjoyed working through your tutorial and realized how crazy my own code was with an emit up to the root scope, followed by a broadcast down. But because I use async http requests for fresh content, I needed to use the $scope otherwise the received content was not updated because nothing fired the digest cycle. I will try to at least remove the up/down event message broadcasting.

    ReplyDelete
    Replies
    1. Andy,

      If you are using $http or $resource the result should automatically return within a digest loop. If you are external, you simply wrap your functionality in an $apply. What I do is create a service that depends on the $rootScope and exposes an "apply" method, then inject that service so again I'm using the service instead of relying on $scope. You can then mock that service in your tests. I hope that makes sense!

      Delete
  7. Hello

    I am trying to implement your example of using service instead on $broadcast or $emit. If i understand that your solution works only when property is bounded to UI, because then it attached a $watcher to itself and listen the changes, but we are not bound that property e.g. selectedGender to any UI then how would you listen the changes. Do we need to attach a $watcher or do you have some other solution for that. Because in ma solution i don't require selected product to bind with any UI.

    Hope you understand my concern...

    ReplyDelete
    Replies
    1. Hi Ravi! Thanks for writing. I do think I understand, and if my understanding is incorrect please let me know. I believe you are pointing out my solution is designed to synchronize based on UI-bound items. What I'm relying on is the digest loop to request a property and that triggers the logic for updates. You are correct. If you are looking to do something differently, i.e. messaging outside of the digest loop, I recommend implementing a promise-based service instead like I describe in this post: Real-time communication in Angular JS. It discusses events sourced via SignalR but they can just as easily be sourced from within your app.

      Delete
    2. okay thanks for reply..

      My question is do we need to attach a $watcher to another controller if we need to get the selectedItem, when selectedItem is not bind with any UI.

      Delete
    3. Ravi, you shouldn't have to use $watch at all, but it would help if you could maybe post a fiddle or snippet so I can see the code and I can let you know how it would look without relying on $scope. Thanks!

      Delete
    4. Hi Jeremy, I think I understand Ravi's question, because I face the same problem. In this post, you're demonstrating this pattern only with UI-bound properties (i.e. 'watches' gets updated when the value changes, and so does the label). However, how would you listen to changes from a controller (or different type that has a dependency on the service), but is not UI-bound?

      funtion MyController(genderService) {
      this.genderService = genderService;

      //This callback should fire when a different
      //gender is selected.
      function onGenderChanged(newValue) {
      //Do something...
      }
      }

      Delete
    5. Right, understood. That's when you'd use a pub/sub type mechanism to notify (or as an alternative, you could wrap the built-in broadcast functions in a service just to remove the coupling to $scope).

      Thanks,

      Jeremy

      Delete
  8. Thanks for the tips Jeremy. Is the same thing achieved, without the Object.defineProperty, by having a function exposed in the controller api ? eg

    this.getSomething = function () {
    return this.svc.getSomething();
    }

    the html binding needs to call the function instead of a getter.

    Both approaches result in multiple calls being made! ... i dont know why this is preferable to an observer ... aka multicast delegate, which would be simple to construct. Can you comment on this?

    ReplyDelete
  9. Simon,

    Yes. The binding can call the function instead. The digest cycle in 1.x really just depends on the evaluation of expressions so whether it is evaluating a function call or a property I believe it's the same overhead. Having said that, I prefer properties because it hides the implementation details better - I'm implementing it in a way to trigger changes but I don't like changing it from a property to a function just because of that. This is moot in Angular 2.0 because it uses a completely new mechanism that is far more efficient.

    Thanks!

    ReplyDelete