Code Reuse in AngularJS

Josh Caldwell, Developer

As I discussed in my last blog post about $compile, AngularJS directives provide a nice way to encapsulate and reuse functionality. Directives can be used in several ways:

  • As a single functional item with a template (for example): any of the Bootstrap widgets as implements by Angular UI Bootstrap
  • As a decorator to other directives, to encapsulate shared functionality
  • As a component which provides child / additional functionality to parent directives

I'm sure there are other patterns / uses of directives that I've failed to mention, but these are the ones I'd like to focus on. I've created a simple angular app based around Leaflet, a popular mapping library. The source code for this lives in this jsfiddle.   Please let me know if there are improvements I can make to the fiddle to better demonstrate best practices / concepts by commenting on this blog.

The end result will allow us to write html like this:

[xhtml]<map center="[40, -90]" zoom="3">
  <circle latlng="[38, -78]" radius="20"></circle>
  <marker latlng="[40, -85]"></marker>
  <marker latlng="[22, -105]"></marker>
  <marker latlng="[35, -120]"></marker>

And end up with nice Leaflet maps like this:

If you're not sure what a directive is, checkout the angular documentation to learn a bit more before continuing.

In our example (see jsfiddle above), we're creating a generic map directive which we can then place throughout our application. I've also created a few "sub-directives" which work with the map and expand its functionality.

To create Markers and Circles on the map, I've created two directives which are intended to live within the map element. They listen for an event called 'mapAvailable' and then add their respective pieces to the map element like so:

[js]scope.$on('mapAvailable', function(e, map) {

This is necessary because they are linked before the parent directive is, so the Leaflet Map object isn't available yet. Once the map directive has finished setting the map up, it broadcasts this event down to its children:


along with a reference to the map. This creates a flow that looks like:


It is worth noting that the core angular code generally uses a different pattern (e.g. Input Directive) for this type of relationship between directives. You can require a controller from your parent in your directive's declaration, which provides an interface for interacting with the parent. This allows there to be an explicit relationship between the directives and ensure that the parent directive is there and of an acceptable type. However, it can make it a bit harder to do free-form things as mentioned above.

I rewrote the example from above using this pattern instead of the event system in this jsFiddle to demonstrate and below is a diagram to show the control flow here:


The last thing to talk about is the latlng directive.  his is a decorator directive which calls setLatLng on scope.shape if and when exists.  It doesn't have its own scope so it just uses the scope of the directive its added to.  For example:

[xhtml]<circle latlng="[38, -78]" radius="20"></circle>[/xhtml]

Here it is using the scope of the Circle directive since it is on the same element. Decorator directives like this take shared code and options / attributes out of other, similar directives and centralize them in a single place. In a similar way, shared Controllers are also useful for sharing functionality between directives.

In conclusion, directives provide a very nice way of encapsulating and reusing functionality in a web application. Depending on what type of directive is being created, an event based message system or a required controller can allow children directives to pass messages back and forth with the parent and interact with shared resources and elements. Directives can also be used in a decorator pattern to extend other directives with a set of related shared functionalities and attributes.

Hopefully, this is a helpful walk through of a few ways of putting directives together and abstracting shared code. Please leave a comment and let me know if there are things I'm missing or just flat out doing wrong here.

Josh Caldwell