Search code examples
javascriptangularjsangularjs-ng-repeatangularjs-track-by

ngRepeat track by: How to add event hook on model change?


I have a simple ngRepeat like the following:

<some-element ng-repeat="singleRecord in arrayOfRecords track by singleRecord.id">
    <!-- stuff -->
</some-element>

arrayOfRecords is updated from a server and may contain new data.

ngRepeat's track by feature can figure out when a new element is added to the array and automatically updates the DOM without changing the existing elements. I would like to hook into that code and execute a callback function when there's new data coming in or old data is removed. Is it possible to easily do this via Angular?

From what I understand, there's a $$watchers which triggers callbacks whenever there's changes to certain variables, but I don't know how to go about hacking that. Is this the right direction?


NOTE: I know I can manually save the arrayOfRecords and compare it with the new values when I fetch them to see what changed. However, since Angular already offers a track by feature which has this logic, it would be nice if I can have Angular automatically trigger an event callback when an element is added or removed from the array. It doesn't make sense to duplicate this logic which already exists in Angular.


Solution

  • Probably you could create a directive and add it along with ng-repeat, so the directive when created(when item is added by ng-repeat) will emit an event and similarly when the item is destroyed it will emit another event.

    A simple implementation here:

    .directive('tracker', function(){
      return{
        restrict:'A',
        link:function(scope, el, attr){
          scope.$emit('ITEM_ADDED', scope.$eval(attr.tracker))
          scope.$on('$destroy', function(){
            scope.$emit('ITEM_REMOVED', scope.$eval(attr.tracker))
          });
        }
      }
    });
    

    and use it as:

    <some-element 
           ng-repeat="singleRecord in arrayOfRecords track by singleRecord.id"
           tracker="item">
    

    and listen for these events at the parent controller for example.

    Demo

    Or using function binding but in a different way, without using isolate scope for that.

    .directive('tracker', function() {
      return {
        restrict: 'A',
        link: function(scope, el, attr) {
          var setter = scope.$eval(attr.tracker);
          if(!angular.isFunction(setter)) return;
          setter({status:'ADDED', item:scope.$eval(attr.trackerItem)});
    
    
          scope.$on('$destroy', function() {
             setter({status:'REMOVED', item:scope.$eval(attr.trackerItem)});
          })
        }
      }
    });
    

    Demo


    The one above was specific to your question since there is no other built in way, Note that if you were to really find out the items added/removed, you could as well do it in your controller by diffing the 2 lists. You could try use lodash api like _.unique or even simple loop comparisons to find the results.

      function findDif(oldList,newList){
           return {added:_.uniq(newList, oldList), removed:_.uniq(oldList, newList)};
     }
    

    Demo