Cleanly declaring AngularJS services with CoffeeScript

, , ,

I’ve recently fallen in love with AngularJS for building JavaScript applications. I’ve also come to rely on CoffeeScript to provide a more comfortable, concise syntax than plain JavaScript. It’s common to define many services when writing AngularJS applications. Services are just plain JavaScript objects that are registered with AngularJS’ dependency injection system, which makes them available to other parts of your application. If you have experience with Java, this will be familiar from Spring or Guice. I prefer to have very little logic in my controllers, and instead create a domain model from layers of services that each have their own responsibility.

One thing I’ve gone back and forth on is how best to declare AngularJS services using CoffeeScript. Let’s imagine a somewhat contrived example service named Tweets that depends on the built-in $http service. When it’s constructed it saves a timestamp, makes an HTTP call and saves the result to a property. Here’s a pretty straightforward way to declare this service using CoffeeScript’s class syntax:

app.service 'Tweets', ['$http', class Tweets
  constructor: (@$http) ->
    @timestamp = Date.now() - 900000
    @getNewTweets()

  getNewTweets: ->
    request = @$http.get '/tweets', params: { ts: @timestamp }
    request.then (result) =>
      @tweets = result.data
      @timestamp = Date.now()
]

This works pretty well. The first time this service is needed in the application, AngularJS will call new Tweets() and save that one (and only one) instance to be provided to any other code that requests it. Because AngularJS calls the Tweets constructor, the first call to getNewTweets is performed right away. But one thing bugs me - a reference to $http is saved as a property of the Tweets instance in the constructor. First, this results in some ugly-looking code like @$http thanks to AngularJS’ habit of naming services with $. It gets even worse when you depend on a lot of different things, and you now have to refer to them all via @ (which is this in CoffeeScript, by the way). Second, this exposes the $http service to anything that depends on Tweets. Anything that depends on Tweets can simply call Tweets.$http, which is a violation of encapsulation. It doesn’t look too bad in this example, but imagine a deep chain of services, each with its own responsibility, declared with this method. Any caller could reach down into that chain and pull out dependencies of dependencies. Or worse, they could change the reference to $http to point to something else.

Let’s try something else. If we are only ever going to have one instance of this class, why have a class at all? We can use the factory method instead of servicefactory just calls the function you provide instead of calling new on it - and have our factory function return a literal object:

app.factory 'Tweets', ['$http', ($http) ->
  obj = 
    timestamp: Date.now() - 900000

    getNewTweets: ->
      request = $http.get '/tweets', params: { ts: @timestamp }
      request.then (result) =>
        @tweets = result.data
        @timestamp = Date.now()

  obj.getNewTweets()

  obj
]

We’ve solved the problem of exposing $http, since now it’s simply captured by the function closure of our getNewTweets function, making it visible to that method but not elsewhere. However, we now have to assign our object to a variable so that we can do our initialization outside the object declaration before returning it. This is ugly, and it feels out of order. A more subtle problem is that stack traces and object dumps in the console will print this as Object, wheras in the first example it will say Tweets. Never underestimate the value of clear debug output.

There’s also a potential for confusion around CoffeeScript’s function binding fat arrow =>, which will bind to the this of the factory function if it’s used when declaring methods on the object literal. I actually ran into this problem when I wanted to bind a function to its object so it could be passed as a callback to another function, and ended up with it bound to the wrong this.

Note that I can declare timestamp as a property of the object literal in this form, wheras in the first version I set it in the constructor - this is because when CoffeeScript creates a class, all the properties are set on the prototype, not the instance, so you can’t store per-instance state there. Well, you can get away with it because AngularJS will only make one instance of your class, but you’re still modifying the prototype rather than the real instance, which isn’t what you want. It pays to understand exactly what CoffeeScript is generating for you.

Let’s go back to using class and try again:

app.factory 'Tweets', ['$http', ($http) ->
  class Tweets
    constructor: ->
      @timestamp = Date.now() - 900000
      @getNewTweets()

    getNewTweets: ->
      request = $http.get '/tweets', params: { ts: @timestamp }
      request.then (result) =>
        @tweets = result.data
        @timestamp = Date.now()

  new Tweets()
]

The difference between this and our first example are subtle. First, we’re using factory instead of service, like in the second example. This means instead of having our dependencies provided to the Tweets constructor, they’re provided to the factory function, and our declaration of the Tweets class can simply close over those parameters, meaning we no longer need to take them as constructor arguments. That solves the problem of $http being visible to other objects, and means we don’t need to write @$http. As I’ve explored AngularJS, I’ve actually found myself always using factory instead of service because it provides more flexibility in how I provide my service value.

However, we also have to be very careful to remember to actually instantiate our class at the end, since AngularJS won’t be doing it for us. That could be really easy to miss after a long class definition.

We can change this a little bit to make it better:

app.factory 'Tweets', ['$http', ($http) ->
  new class Tweets
    constructor: ->
      @timestamp = Date.now() - 900000
      @getNewTweets()

    getNewTweets: ->
      request = $http.get '/tweets', params: { ts: @timestamp }
      request.then (result) =>
        @tweets = result.data
        @timestamp = Date.now()
]

Now we call new right away to instantiate the class we’re defining. I think this is very clear, and there’s nothing after the class definition to forget. This is the pattern I’ve settled into whenever I declare singleton services – it’s clean, readable and concise, and it solves all the problems we’d identified:

  1. Dependencies of our service are not exposed.
  2. Our service will print with a meaningful name in the console.
  3. There is no out-of-order code, or extra code after the class declaration.
  4. We never have to put the characters @ and $ next to each other. Sorry, I’m still recovering from exposure to Perl and I can’t take it.

I’ve written up a more detailed example in this gist, which shows the pattern I’ve been using to produce ActiveRecord-style APIs with private dependencies. I hope this helps you write clean, understandable AngularJS services in CoffeeScript.

comments powered by Disqus
I'm Benjamin Hollis, a software developer in Seattle. Check out my website.