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:
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.
Assign the original object's $$id
to the transformed objects. This seems hackish, based on my understanding $$id
is supposed to be read only.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With