Take the 2-minute tour ×
Stack Overflow is a question and answer site for professional and enthusiast programmers. It's 100% free, no registration required.

I have an array of objects getting assigned to $scope within a controller, and is being filtered in a series of divs within a partial template:

  <div class="entity-list">
    <!-- Folders -->
    <div class="entity-listing folder" ng-repeat="child in folders | filterName:nameFilter | entityType:filterType | orderBy:orderProp:orderAscDesc">
      <!-- Some HTML -->
    </div>
    <!-- Files -->
    <div class="entity-listing document" ng-repeat="child in documents | filterName:nameFilter | entityType:filterType | orderBy:orderProp:orderAscDesc">
      <!-- Some HTML -->
    </div>
  </div>

The filters are displayed in a separate fieldset element:

  <fieldset id="filters">
    <legend>Filters</legend>
    <label for="filter-name">Name Contains:</label>
    <input id="filter-name" ng-model="nameFilter">
    <label for="filter-type">Show:</label>
    <select id="filter-type" ng-model="filterType">
      <!-- some options -->
    </select>
    <label for="sort-field">Sort By:</label>
    <select id="sort-field" ng-model="orderProp">
      <!-- some options -->
    </select>
    <select ng-model="orderAscDesc">
      <!-- some options -->
    </select>
  </fieldset>

I have a module set up with two filters, and pass that module on to my app:

angular.module('widget', ['filters']).
  config(['$routeProvider', function($routeProvider) {
    $routeProvider.
      when('/homefolder', {templateUrl: 'widget/partial/entity-list.html', controller: HomeFolderCtrl}).
      when('/folder/:uuid', {templateUrl: 'widget/partial/entity-list.html', controller: FolderCtrl}).
      otherwise({redirectTo: '/homefolder'});
  }]);
angular.module('filters', []).
  filter('entityType', function() {
    return function(items, type) {
      var returnArray = [];
      for (var i=0,ii=items.length;i<ii;i++) {
        if (type == "both") {
          returnArray.push(items[i]);
        } else if (items[i].type == type) {
          returnArray.push(items[i]);
        }
      }
      return returnArray;
    }
  }).
  filter('filterName', function() {
    return function(items, str) {
      var returnArray = [];
      if (str != '') {
        for (var i=0,ii=items.length;i<ii;i++) {
          if (items[i].name.indexOf(str) !== -1) {
            returnArray.push(items[i]);
          }
        }
      } else {
        returnArray = items;
      }
      return returnArray;
    }
  });

I'm concerned that this code works, yet when looking at the error console, I get a bunch of errors saying Cannot read property 'length' of undefined regarding the filterName and entityType filters' for loops. If I wrap those filter in an if statement to check if items is defined (take for example filterName):

  filter('filterName', function() {
    return function(items, str) {
      var returnArray = [];
      if (items) {
        if (str != '') {
          for (var i=0,ii=items.length;i<ii;i++) {
            if (items[i].name.indexOf(str) !== -1) {
              returnArray.push(items[i]);
            }
          }
        } else {
          returnArray = items;
        }
      }
      return returnArray;
    }
  });

It gets rid of the error and works as well. Why would AngularJS pass in undefined items to the filters? Where else could these filters be getting called if they are only explicitly getting called in my two ng-repeats?

share|improve this question
 
because you may want to deal with undefined or null in your filters, it is a sensible design choice IMO (you can write a default filter that check null or undefined and replace with a provided value for exemple: yourArrayPromise | default: [] –  Guillaume86 Nov 13 '12 at 17:07
add comment

1 Answer

up vote 3 down vote accepted

Is it safe to assume that the data going through your filters is retrieved from the server asynchronously? When the page first renders, and angular goes through everything, there is no data yet, so it is undefined. Once the data returns, it goes through the digest cycle again, and then there is data this time, so it all works. For this reason, it's a good idea to have the defined check at the beginning of the filter function.

share|improve this answer
 
Yes, the folders and documents scope arrays are retrieved asynchronously. I would have assumed that the ng-repeat (and subsequent filters there-in) would only execute if the arrays existed... I guess not? –  Scott Nov 13 '12 at 16:18
1  
Maybe you want your filter to do something with the value if it's undefined? This kind of thing irked me when I was getting started, but I've come to accept it as necessary. One thing you can do is initialize the variable to an empty array when the controller starts or in an ng-init directive, e.g. <div class="entity-list" ng-init="folders = []"> –  Brandon Tilley Nov 13 '12 at 17:02
 
Excellent, initializing the values in the controller makes more sense then checking with if statements in the filters. Thanks! –  Scott Nov 13 '12 at 17:56
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.