Tuesday, July 3, 2012

Working together: AngularJS and Coffeescript

While building my first AngularJS application, I followed the official tutorial, and also other useful examples I found on the web. The examples were all in javascript of course, which was fine in the beginning, but after a while I started to miss the elegance of Coffeescript's syntax.

Changing an AngularJS app from javascript is not as straight forward as changing the syntax. The reason for this is the Coffeescript compiler's reverence for the global scope. All coffee script code is wrapped in a function to protect the global scope from pollution and conflict.

Take the following javascript controller definition from the official tutorial as an example. If you simply convert this to Coffeescript you will get a PhoneListCtrl not found exception in your browser console.


function PhoneListCtrl($scope) {
  $scope.phones = [
    {"name": "Nexus S",
     "snippet": "Fast just got faster with Nexus S."},
    {"name": "Motorola XOOM™ with Wi-Fi",
     "snippet": "The Next, Next Generation tablet."},
    {"name": "MOTOROLA XOOM™",
     "snippet": "The Next, Next Generation tablet."}
  ];
}

PhoneListCtrl = ($scope) ->
  $scope.phones = [
    {"name": "Nexus S",
     "snippet": "Fast just got faster with Nexus S."},
    {"name": "Motorola XOOM™ with Wi-Fi",
     "snippet": "The Next, Next Generation tablet."},
    {"name": "MOTOROLA XOOM™",
     "snippet": "The Next, Next Generation tablet."}
  ]

The quickest solution is to deliberately attach the controller to the window scope, thus counteracting the protection to the global scope given by Coffeescript compiler. It is as simple as that!


window.PhoneListCtrl = ($scope) ->
  $scope.phones = [
    {"name": "Nexus S",
     "snippet": "Fast just got faster with Nexus S."},
    {"name": "Motorola XOOM™ with Wi-Fi",
     "snippet": "The Next, Next Generation tablet."},
    {"name": "MOTOROLA XOOM™",
     "snippet": "The Next, Next Generation tablet."}
  ]

BUT, for those who like to follow best practices, and avoid the sneering of other developers the above solution might not be enough. If this is the case, you can make use of the global angular object to register a controller for a given module.


angular.module('phoneApp').controller 'PhoneListCtrl', ($scope) ->
  $scope.phones = [
    {"name": "Nexus S",
     "snippet": "Fast just got faster with Nexus S."},
    {"name": "Motorola XOOM™ with Wi-Fi",
     "snippet": "The Next, Next Generation tablet."},
    {"name": "MOTOROLA XOOM™",
     "snippet": "The Next, Next Generation tablet."}
  ]

This registers the controller alright, but you might find you are still seeing the controller not found error. The reason for this is that the code using your controller expected it to be operating at the global scope as it is in the Angular tutorial.


angular.module('phonecat', []).
  config(['$routeProvider', function($routeProvider) {
  $routeProvider.
      when('/phones', {templateUrl: 'partials/phone-list.html',   controller: PhoneListCtrl}).
      when('/phones/:phoneId', {templateUrl: 'partials/phone-detail.html', controller: PhoneDetailCtrl}).
      otherwise({redirectTo: '/phones'});
}]);

Fortunately AngularJS comes to our rescue in the most elegant of ways. We simply have to change the reference to the controller to be a string , and we have lift off!


angular.module('phonecat', []).
  config(['$routeProvider', function($routeProvider) {
  $routeProvider.
      when('/phones', {templateUrl: 'partials/phone-list.html',   controller: 'PhoneListCtrl'}).
      when('/phones/:phoneId', {templateUrl: 'partials/phone-detail.html', controller: PhoneDetailCtrl}).
      otherwise({redirectTo: '/phones'});
}]);

Sunday, July 1, 2012

AngularJs: User friendly date display with AngularJs and MomentJs

Whether you checkout Twitter or any of the other uber popular web apps, you will find dates displayed are often in a very user friendly "from now" format. Examples of this format are "Just now", "A few minutes ago", and "2 days ago". In these three examples a date value is displayed as an approximation of the difference between the absolute date value and the current time. This "from now" format could be applied on the server, but it is generally better to apply this on the client side via javascript so that it can be continually updated to reflect the every passing time. 2 javascript examples I've come across for applying the "from now" format are John Resig's lightweight pretty date library, and the other is more built-out Moment.js. In this post I explain how to apply a sliver of the power of MomentJs in an AngularJs application.
AngularJs is a delightful Client side javascript framework. One of its features is called "filters". Filters can be succinctly applied to a collections or particular values. Here is a quick example of using the built uppercase filter to produce HELLO WORLD.

{{ 'Hello World' | uppercase}}
AngularJs even comes with a built-in date filter which is great for producing typical absolute value date formats, but unfortunately it doesn't come with a "from now" format.

{{ date_expression | date[:format] }}
Lucky for us, creating our own filter is pretty easy once you know how. The following code injects a filter called fromNow into the module myModule.

angular.module('myModule').
  filter('fromNow', function() {
    return function(dateString) {
      return moment(dateString).fromNow()
    };
  });
Now all you need to do is apply this filter in your template, you have your very user friendly date format, just like the big guys!

{{ reply.createdDate | fromNow }}