AngularJS: Iterators and Filters

13 Jun 2013 | By Alex Young | Tags angularjs angularfeeds mvc bower

AngularJS has a rich expression-based system for filtering and ordering data based on predicates. The orderBy filter can be used with the ng-repeat directive:

<ul>
  <li ng-repeat="item in stories | orderBy:predicate:date"><a href=""></a></li>
</ul>

Today we’re going to use orderBy inside a controller using dependency injection to organise multiple feeds into a river of news sorted by date.

Iterating in Controllers

Before sorting and displaying stories, we need to collect them into a suitable data structure. An array will suffice (app/scripts/controllers/main.js):

$scope.stories = [];

Next we need to append stories to this collection, but only if they haven’t already been added. Let’s use a function to encapsulate that away from fetching stories:

$scope.fetchFeed = function(feed) {
  feed.items = [];

  var apiUrl = "http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20xml%20where%20url%3D'";
  apiUrl += encodeURIComponent(feed.url);
  apiUrl += "'%20and%20itemPath%3D'feed.entry'&format=json&diagnostics=true&callback=JSON_CALLBACK";

  $http.jsonp(apiUrl).
    success(function(data) {
      if (data.query.results) {
        feed.items = data.query.results.entry;
      }
      addStories(feed.items);

The addStories function just needs to loop over each feed item to determine if it’s already been added to $scope.stories. The angular.forEach API in module ng is the perfect way to do this:

function addStories(stories) {
  var changed = false;
  angular.forEach(stories, function(story, key) {
    if (!storyInCollection(story)) {
      $scope.stories.push(story);
      changed = true;
    }
  });
}

As you can see, forEach accepts an array and a function to call for each item. The storyInCollection function now needs to loop over each existing story to see if it’s already been added. Figuring out which story is unique is easy because feeds have an id value:

function storyInCollection(story) {
  for (var i = 0; i < $scope.stories.length; i++) {
    if ($scope.stories[i].id === story.id) {
      return true;
    }
  }
  return false;
}

Storing Data

Now we’ve got a list of stories in our scope, we need to sort them by date just like a real feed reader. Whenever addStories changes the list of stories we should sort it. AngularJS doesn’t really have any fancy functional methods like map or some, which you can find in ECMAScript 5 anyway, but it does provide API access to the filtering and sorting modules that are typically used in templates.

To access this functionality you’ll need to load $filter:

angular.module('djsreaderApp')
  .controller('MainCtrl', function($scope, $http, $timeout, $filter) {

$filter will return a function that knows how to sort or filter arrays. That means you need to call it with the name of the desired method, then call the value returned with an array and an expression: $filter(filter)(array, expression). To add sorting to our feeds, call $filter()() and update the $scope.stories array:

// At the end of addStories
if (changed) {
  $scope.stories = $filter('orderBy')($scope.stories, 'date');
}

The only thing left to do is update the template in app/views/mail.html:

<ul>
  <li ng-repeat="item in stories"><a href=""></a></li>
</ul>

If you add multiple feeds using the app’s web interface you should see them combined into a river of news.

Conclusion

The river of news view

You can find this code in commit ff4d6a6.


blog comments powered by Disqus