Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJs - ngRepeat with a filter that returns a new object

I'm trying to apply a transformation to the objects in my filter, which results in an array of new objects being returned. This is because I want to filter the objects AFTER the transformation is applied and display the results of the transformation. However, I end up with an infinite digest because the objects I display are different than the objects I put in (when comparing their $$ids). My thoughts to solve this are the following:

  1. Use a tracking expression like track by item.id and assign the original objects' ids to each of the transformed objects. While all my objects currently do have an id, this seems like a bad idea because it makes the filter much less general- the original objects must have an id, the transformation must not set an id (as it will be overwritten), etc.

  2. Assign the original object's $$id to the transformed objects. This seems hackish, based on my understanding $$id is supposed to be read only.

  3. Return a subset of the original objects based on the result of the transformation's filtering. This may cause performance issues as the transformation needs to be applied in both the filter and the display expression, AND I have to loop back through the transformed / filtered items to select the right original ones to return.

Here is the filter:

listModule.filter('ui.filter.transformFilter',
                 ['$filter',
                  '$id',
                   function($filter, $id)
  {
    var Filter = $filter('filter');
    return function(objects, transformer, expression) {
      // precondition- we need a list of objects
      if (!_.isArray(objects)) {
        return objects;
      }

      var transformed = [];
      for (var i = 0; i < objects.length; i++) {
        transformed[i] = transformer(objects[i]);
      }

      return filtered = Filter(transformed, expression);
    }                  
  }]
);

And here is how I am trying to use it:

  <tr ng-repeat="item in list.items | ui.filter.transformFilter:list.transformerFunction:list.search" ng-click="list.select({'item': item})" class="list-item">
    <td ng-repeat="label in list.labels" ng-bind-html="item[label.key]"></td>
  </tr>

Oh, and ideally ngClick returns the original object, but I can always wrap a function around it to look that up.

like image 995
jtfairbank Avatar asked Nov 27 '13 17:11

jtfairbank


1 Answers

One solution to this problem where you have an idempotent function that Angular, due to object IDs, thinks is not idempotent (and thus causes the $digest loop issue as you noted) is to use lo-dash/underscore's _.memoize to cache your function's results.

This will guarantee that for any given cache key your filter will always return a completely identical object (including $$id). This way you don't have to play games with $$id and you get the performance benefit of not having to recompute the filter results on each $digest loop.

Here's how you could cache your filter's results:

return _.memoize(function(objects, transformer, expression) { ... },
                 function(objects,transformer,expression){ 
                    return objects +transformer.name + expression;
                  });   

One important note for your situation is that by default _.memoize uses the first function parameter (objects in this case) as the cache key. Since your filter likely produces different results given different transformer functions and expressions I've added the optional second parameter- a hash function that uses objects,expression, and the name of the transformer function to produce a cache key.

Here's a simplified version of your code using this: fiddle

like image 149
KayakDave Avatar answered Oct 29 '22 23:10

KayakDave