AngularJS — When to broadcast?

How to choose between event emitters and directive attributes

AngularJS provides multiple ways to share information between parts of your application. Our issue at STEAMULO was to find a simple way to choose between using “directive attributes” and “events” to propagate a change.

Here is a guide to help you find the easiest, most performant, and maintainable way to share those state changes across your application.

This article is a follow up to an internal debate that we had at STEAMULO during a weekly Javascript Crew meeting. The original presentation and images were made by Sylvain Gourio.

Data Binding

Data Bindings are the default way of transmitting informations in our applications. We try to keep dependencies to a minimum and have “pure” directives which behaviour depends only on their attributes.

Example

Our “Sangoku” directive has two behaviours.

Its powerLevel displayed and derived from strength and speed.

An Attack move with the sender and its power as attributes

Strength and speed are provided from parent to the directive,. But the directive needs to set a watcher to be able to update the powerLevel any time one of the attribute changes.

Attack is provided from the directive to parent. It allows our directive to call a function in the parent, thus interacting with the rest of the application.

Usage

A binding is perfect when using a main directive and many “dumb” ones: A pattern with a main directive containing the logic and many directives to display UI components is a perfect use of directive attributes.(See this article by Dan Abramov)

It’s easy to test and maintain: Just change a parameter, launch a digest cycle and check if the output in the isolated scope is correct.

A two-way binding is an easy way to propagate a change: It automatically updates the parent, and other directives relying on the same variable.

Gotchas

Transmitting through a lot of parents is cumbersome : If a variable is set in a scope, and needed in its great-grand child directive, every directive in between needs to have the corresponding attribute.

Two-way binding may have unwanted side effects : It makes it easy for any directive to change a variable in its parent, and can have an impact on other child-directives that was not anticipated.

Parent-child directive structure is enforced : Because any attribute must be set by the parent, it’s hard to move a directive around.

A lot of watchers are needed : Any attribute is a new watcher that will be run every digest cycle. Any variable depending on a parameter needs its own watcher to be updated when parameters are updated (see powerLevel in the example). At some point, this could lead to performance issues.

Custom Events

Example

The “vegeta” directive has the same behaviours as “sangoku”

powerLevel changes are handled through a listener, the parent can “broadcast” changes to us.

The attack is sent with “emit” so that the parent receives it.

Usage

State changes impact many parts of the application : One event can replace a set of parent-child bindings and will be more efficient since it won’t be triggered on every digest cycle

Events give more control on state updates : Bindings update everything right away. In the event you can delay or discard the change.

The directive needs to be fully independent : While writing external modules, events are a simpler way to interact since they give more control on the impact of state changes.

Gotchas

We had event all over the place, at one point we implemented event parameters expressing the event’s origin and handle each origin in a different way. That was messed up.

— Our team after their first encounter with events

Events are hard to track : If you need a map of all event origins/outcomes, a search through all the project is necessary… Creating a constant with all event names makes it easier but still.

They need to cover a specific use case, but not too specific : too specific leads to too many events, and not specific enough leads to too much logic in the handler

The difference between emit and broadcast: Emits goes up, broadcast goes down. And if you need anything else, usually it ends up in a “$rootscope.broadcast”.

Our Choice

An event expresses better user actions, especially actions that have an impact across the application.

A data binding expresses better a data change in the app, especially if the directive doesn’t have it’s own logic.

Going further

When an application grows beyond a certain point, handling state becomes hard. Just sharing information in a sensible way is not enough, a better way to store it is also needed.

The internet right now

There are now a few libraries that wish to solve this problem specifically. I recommend learning about both Redux and RxJS. At least the concepts used in both libraries are very interesting to help with state management.

Thanks to the Javascript Crew at STEAMULO for finding the main points explained in the article.