2

I have one directive, which translate dates and times to better - human reading - form, like "now", "one minute ago", ..

I'd like to update it periodically so dates which are translated as "now" will be after one minute updated..

One way how to solve this to make $interval in directive so it keeps updating after 60'000ms but it can be possible bottleneck for pages with large number dates using this directive..

Second thought is to make service, which will broadcast an event heartbeat to $rootScope and directives will bind listeners and updates its times. But it also means that I would traverse through all scopes in app with broadcast event every N seconds..

So the question.. Is there any way, how to make in angular (more pure solution is better) that service will fire events to only one directive?

Something like:

$directive("timeAgo").$emit("hearthbeat", new Date().getTime())
Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Jan Jůna
  • 4,965
  • 3
  • 21
  • 27
  • I would use moment.js, anmd angular-moment for this – ABOS May 13 '15 at 12:58
  • Moment.js and angular-moment are very good tools but I think, that I would not fully utilize them.. But thank you for your answer. – Jan Jůna May 13 '15 at 13:14
  • Some rough ideas: put element ids in different queues, like minute, hour, day etc, so each element belongs to one queue only, and wrap the timing logic in directive controller. You manually move ids from one queue to another once it expires. And you just keep one $interval. – ABOS May 13 '15 at 14:07

3 Answers3

1

You may try to use a filter instead of directive. As I know, AngularJS updates filters every $digest circle. So, you'll not need to use a timer at all. But I'm not sure if I understand how filters work correctly.

As for service, instead of $rootScope.$broadcast you may try to use $rootScope.$emit method. Docs say that $emit sends events upwards while $broadcast sends them downwards. So, with the former one the event will be accessible only on $rootScope and will not be propagated to its children. You can find more information about these methods in official docs or, for example, in this question.

I think, that it might be also possible to use regular callbacks instead of events. You may try in add a method like addTickEvenListener to the service and call only registered callbacks on every timer tick. I'm not sure, but it may be slightly problematic to remove event listeners when a directive destroys. Probably, this problem may be solved by listening to $destroy event of the scope like it is explained here.

Community
  • 1
  • 1
DWand
  • 674
  • 7
  • 16
  • Nice :-) thank you.. This is easy solution for removing callbacks/listeners of destroyed directives and does not messing other scopes.. Now in directive I have: var listener = $rootScope.$on("hearthbeat", function(){ ... }); scope.$on('$destroy', function() { listener(); }); And in service: $rootScope.$emit('hearthbeat', ...); – Jan Jůna May 13 '15 at 14:56
1

Rather than relying on $rootScope, you can create an over-arching wrapper directive that you will use once around all scopes that you want to use smart dates, like this...

app.directive('smartDateMaster', SmartDateMasterDirective);
function SmartDateMasterDirective() {
  return {
    restrict: 'AE',
    scope: true,
    controller: 'SmartDateController'
  };
}

app.controller('SmartDateController', ['$interval', '$scope', SmartDateCntl]);
function SmartDateCntl($interval, $scope) {

  // one interval is set, and the event is broadcast from this scope
  $interval(function() {
    $scope.$broadcast('sdPulse', new Date());
  }, 10000); // 10 seconds, adjust to taste

}

... and then anywhere you want to show one of these smart, relative dates, use a directive that listens for the event and updates accordingly:

app.directive('smartDate', SmartDateDirective);
function SmartDateDirective() {
  return {
    restrict: 'A',
    template: '<span>{{ dateText }}</span>',
    scope: {
      srcDate: '=smartDate' /* a reference to a JS time object */
    },
    link: function($scope, elem, attrs) {

      $scope.$on('sdPulse', function(evt, currentDate) {
        var difference = currentDate.getTime() - $scope.srcDate.getTime();

        // some logic to determine what the date text should be based on difference

        // then set it in scope for display
        $scope.dateText = 'whatever you decided';
      });
    }
  };
}

Implementation in HTML would look something like this, where you wrap all occurrences of Smart Dates with the Smart Date Master directive. Anywhere you want a Smart Date, use the smart-date attribute along with an assignable expression that points to a date in the parent scope.

<body>

  <!-- some outer content unconcerned with dates -->

  <smart-date-master>

    <!-- some inner content with dates spread throughout -->

    <div>
      Jack commented on your status <span smart-date="jackCommentDate"></span>.
    </div>

    <div>
      Jill poked you <span smart-date="jillPokeDate"></span>.
    </div>

    <div>
      John sent you a message <span smart-date="johnMsgDate"></span>
    </div>

  </smart-date-master>

</body>

This should still be a performant solution, and you wouldn't be doing anything non-standard or wildly inefficient.

hartz89
  • 669
  • 1
  • 6
  • 18
  • This thing looks very interesting. But, unfortunately, I failed getting it to work. When I add other directives outside a wrapper, they still hear an event. Maybe, I've missed something? Here is a plunker: http://plnkr.co/edit/EMDArBRTMvucH1MncD4j?p=preview – DWand May 13 '15 at 21:03
  • Good catch- because the "master" directive didn't have its own scope, it was broadcasting the event from whatever scope it was residing in along with all child scopes. Adding `scope: true` to the directive definition fixes the issue, as shown in [this plunkr](http://plnkr.co/edit/Yqsr0KW7EpZ3h47IJ3kI?p=preview). Updating my answer to include this as well. – hartz89 May 13 '15 at 23:14
0

I create a plunker to explain some rough ideas: http://plnkr.co/edit/d8vE0HCLbemgiPNuvl47?p=preview

It is not complete yet since elements are not moved to different queues after         threshold is met.
ABOS
  • 3,723
  • 3
  • 17
  • 23