36

I want to use timeago plugin to make dates look nicer. The problem is that these dates are fetched via AngularJS from the REST dynamically. So, when I attach this jQuery plugin to my page, it just doesn't process it.

So, how to better do such things? I would be happy to run without jQuery at all if possible.

Sergei Basharov
  • 51,276
  • 73
  • 200
  • 335

4 Answers4

103

I would use momentjs - http://momentjs.com/ - it has no dependencies.

Then you can just create a filter called 'timeAgo' or 'fromNow'. You should probably call it fromNow because that's what momentjs calls it:

angular.module('myApp').filter('fromNow', function() {
  return function(date) {
    return moment(date).fromNow();
  }
});

Then you would just use simple data binding in your view:

<p>Pizza arrives {{pizzaArrivalDate | fromNow}}</p>

If you really wanted to use the jQuery plugin, you would probably have to write a directive. But that way of doing it is bad because it links your data to a DOM element. The way I put above seperates data from DOM which is the angular way. And it's pretty :-D

Andrew Joslin
  • 43,033
  • 21
  • 100
  • 75
  • 2
    ...but this solution does not make the time get updated as time progresses, right? – Ztyx Apr 25 '13 at 08:22
  • It does not. You would want a directive for that, one that would simply refresh the element every minute or so (you don't want it to cause a scope.$apply every minute, just update the element's text). – Andrew Joslin Apr 25 '13 at 18:27
  • 1
    Took a while to figure out how to do it. Using the directive posted by "kwon" did not do it for me. Turns out, I had to watch the title attribute. See https://gist.github.com/JensRantil/5470122 for an example. – Ztyx Apr 26 '13 at 20:21
  • 2
    It should be noted that referencing `moment` from a global variable like that isn't really inline with best practices. injecting `$window` and getting it via `$window.moment` would be better... *Ideally*, however, you'd create a service for `moment` that you could inject into your filter. This would allow you to change your implementation at a later point via DI, and/or unit test more effectively. ... food for thought. – Ben Lesh Oct 21 '13 at 20:12
  • ^yes, that is how it would be made testable. kudos for blesh – Andrew Joslin Oct 22 '13 at 11:28
  • If you want a module that already does this for you, check out my answer below about angular-moment (including a usage example). – urish Nov 01 '13 at 04:34
20

You can use the am-time-ago directive from angular-moment module (which is based on Moment.js).

It does not require jQuery, and the time get updated as time progresses. Example:

<span am-time-ago="lastLoginTime"></span>

The content of the above span will be replaced with a relative time string, for example "2 hours ago", and will be automatically updated as time progresses.

Alex
  • 1,425
  • 11
  • 25
urish
  • 8,943
  • 8
  • 54
  • 75
  • 1
    Great find urish! Works and updates live as stated, note: Don't forget to add the dependency in your app, see the angular-moment docs. – Andrew Lank Nov 01 '13 at 00:42
  • 1
    @urish: Is there any way to use `am-time-ago` with a custom format, e.g. "2h" instead of "2 hours ago"? – Misha Moroshko Apr 09 '14 at 11:33
  • See here for an answer about customizing the format of `am-time-ago`: http://stackoverflow.com/questions/22964767/how-to-format-angular-moments-am-time-ago-directive – urish May 13 '14 at 21:00
6

moment.js is the best choice. I have created a generic moment filter that allow you to use any moment formatting method.

angular.module('myApp', [])
  .filter('moment', [
    function () {
      return function (date, method) {
        var momented = moment(date);
        return momented[method].apply(momented, Array.prototype.slice.call(arguments, 2));
      };
    }
  ]);

Use it like

<div>{{ date | moment:'fromNow' }}</div>
<div>{{ date | moment:'calendar' }}</div>

You can checkout it in action here http://jsfiddle.net/gregwym/7207osvw/

Greg Wang
  • 1,357
  • 1
  • 13
  • 17
4

If you need jQuery, writing a directive/filter is the way to go.

app.directive("timeAgo", function($compile) {
  return {  
    restrict: "C",
    link: function(scope, element, attrs) {
      jQuery(element).timeago();
    }
  };
});

app.filter("timeAgo", function() {
  return function(date) {
    return jQuery.timeago(date); 
  };
});

Directive and/or Filter (jsbin)

nowk
  • 32,822
  • 2
  • 35
  • 40
  • Using a directive is the only way to have the datetime automatically update as time progresses. – Ztyx Apr 25 '13 at 08:42
  • 1
    The "$compile" dependency is unnecessary for the directive. – Ztyx Apr 26 '13 at 20:08
  • The above directive does not handle the case when the datetime in the "title" attribute is generated "{{ ... }}" syntax. See https://gist.github.com/JensRantil/5470122 for an improvement that fixes this. – Ztyx Apr 26 '13 at 20:10