Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Show an aggregated list in angularjs

In my model I have data similar to:

$scope.list = [{id:0,tags:['tag1','tag2']},{id:2,tags:['tag2']}};

I want to show a list of tags (contains unique values of 'tag1' and 'tag2') with checkboxes. Hopefully something like:

<div ng-repeat="tag in list.tags">
    <label class="checkbox">
        <input type="checkbox" ng-model="filter.tag" />
        {{tag}}
    </label>
</div>

I know how to filter the main list based on whats checked if I hard code the list, but not how to generate the list of unique tags automatically.

like image 465
Joel Lucsy Avatar asked Jan 04 '13 18:01

Joel Lucsy


1 Answers

You are looking to perform three operations:

  • Get the array of tags from each item in $scope.list
  • Flatten these into a single array
  • Get the unique values from this array

You can do this with pure JavaScript, but to make things easier, I would recommend using Underscore, a library that gives you access to many functions for manipulating and inspecting arrays, objects, and so forth.

Let's start with this code:

$scope.list = [
  {id: 0, tags: ['tag1', 'tag2']},
  {id: 1, tags: ['tag2']},
  {id: 2, tags: ['tag1', 'tag3', 'tag4']},
  {id: 3, tags: ['tag3', 'tag4']}
];

Now, let's perform the first operation: get the array from the tags property for each object in $scope.list. Underscore provides the pluck method, which is just what we need.

pluck _.pluck(list, propertyName)

A convenient version of what is perhaps the most common use-case for map: extracting a list of property values.

Using pluck, we can get the following:

var tags = _.pluck($scope.list, 'tags');
// gives us [['tag1', 'tag2'], ['tag2'], ['tag1', 'tag3', 'tag4'], ['tag3', 'tag4']]

Now, we want to flatten that array.

flatten _.flatten(array, [shallow])

Flattens a nested array (the nesting can be to any depth). If you pass shallow, the array will only be flattened a single level.

tags = _.flatten(tags);
// gives us ['tag1', 'tag2', 'tag2', 'tag1', 'tag3', 'tag4', 'tag3', 'tag4']

Finally, you only want one instance of each tag.

uniq _.uniq(array, [isSorted], [iterator]) Alias: unique

Produces a duplicate-free version of the array, using === to test object equality. If you know in advance that the array is sorted, passing true for isSorted will run a much faster algorithm. If you want to compute unique items based on a transformation, pass an iterator function.

tags = _.unique(tags)
// gives us ['tag1', 'tag2', 'tag3', 'tag4']

We can combine these together with Underscore's useful chain method to chain these together. Let's create a function on the scope that returns the unique tags:

$scope.uniqueTags = function() {
  return _.chain($scope.list)
    .pluck('tags')
    .flatten()
    .unique()
    .value();
};

Since this is a function, it will always return the unique tags, no matter if we add or remove items in $scope.list after the fact.

Now you can use ng-repeat on uniqueTags to show each tag:

<div ng-repeat="tag in uniqueTags()">
  <label class="checkbox">
    <input type="checkbox" ng-model="filter.tag" />
    {{tag}}
  </label>
</div>

Here is a working jsFiddle that demonstrates this technique: http://jsfiddle.net/BinaryMuse/cqTKG/

like image 140
Michelle Tilley Avatar answered Sep 28 '22 20:09

Michelle Tilley