4

I have a pretty simple textbox filtering an ng-repeat on some unordered lis. When I add a value to the textbox the items with the null values are removed and do not return even when the textbox is cleared. I have an idea of why this is happening (the search object now has an empty property which doesn't match the nulls), but I cannot figure out how to solve the problem. I've tried to pop() the property off of the search object with no luck.

HTML:

<div ng-controller="ListCtrl">
    <input type="text" ng-model="search.age" placeholder="Age"></input>
    <ul>
        <li ng-repeat="item in items | filter:search">
            {{item.name}} - {{item.age}}
        </li>
    </ul>
</div>

JS:

function ListCtrl($scope) {
  $scope.items = [
    {'name':'Carl', 'age':69},
    {'name':'Neil', 'age':54},
    {'name':'Richard'},
    {'name':'Chris', 'age':58}
  ];
}

Please checkout the JSfiddle to better illustrate the issue.

Cœur
  • 37,241
  • 25
  • 195
  • 267
Jesse
  • 2,043
  • 9
  • 28
  • 45

4 Answers4

4

I figured it out with the help of this answer. If I just add an ng-change to the textbox I can watch for an empty value and delete the property.

HTML:

<input type="text" ng-model="search.age" ng-change="clear()" placeholder="Age"></input>

JS:

$scope.clear = function(){
    if($scope.search.age.length == 0){
        delete $scope.search.age;
    }
}

Updated fiddle. I am aware the current if prevents a user from filtering on a single space, but so far this does not seem to cause a problem for me.

BONUS: ! will return all null values and !! will return all not null values.

Community
  • 1
  • 1
Jesse
  • 2,043
  • 9
  • 28
  • 45
4

The cleanest solution I have found is writing a custom directive to modify the input field behaviour like this:

app.directive('deleteIfEmpty', function () {
    return {
        restrict: 'A',
        scope: {
            ngModel: '='
        },
        link: function (scope, element, attrs) {
            scope.$watch("ngModel", function (newValue, oldValue) {
                if (typeof scope.ngModel !== 'undefined' && scope.ngModel.length === 0) {
                    delete scope.ngModel;
                }
            });
        }
    };
});

And use it as follows:

 <input type="text" ng-model="filter" delete-if-empty>
Ben Torfs
  • 41
  • 2
2

Modify the input ng-model:

<input type="text" ng-model="searchObj.age" placeholder="Age"></input>

Add this to your controller:

$scope.searchObj = {
}

And either of these will work in your html repeat:

ng-repeat="item in items | filter: searchObj.age"

Or

ng-repeat="item in items | filter: {age: searchObj.age || undefined}"

jsfiddle

tbmpls
  • 221
  • 3
  • 8
0

You won't be able to use filter:search. Looking at the Angular code, if your obj with an undefined age gets filtered (even when the input is empty) it will fall through this switch statement and always return false. This switch doesn't get called the first time your ng-repeat is run because $scope.search.age is undefined. After your first entry into the input and clearing it out, now $scope.search.age is an empty string...so the filter will always be run.

  switch (typeof obj) { ***<-- obj is undefined when you have a missing age***
    case "boolean":
    case "number":
    case "string":
      return comparator(obj, text);
    case "object":
      switch (typeof text) {
        case "object":
          return comparator(obj, text);
        default:
          for ( var objKey in obj) {
            if (objKey.charAt(0) !== '$' && search(obj[objKey], text)) {
              return true;
            }
          }
          break;
      }
      return false;
    case "array":
      for ( var i = 0; i < obj.length; i++) {
        if (search(obj[i], text)) {
          return true;
        }
      }
      return false;
    default:
      return false;  ***<--falls through and just returns false***
  }

You can try writing your own filter function, something like this.

http://jsfiddle.net/wuqu2/

<div ng-controller="ListCtrl">
    <input type="text" ng-model="search.age" placeholder="Age"></input>
    <ul>
        <li ng-repeat="item in items | filter:checkAge">
            {{item.name}} - {{item.age}}
        </li>
    </ul>
</div>

  $scope.checkAge = function(item)
  {
      if($scope.search && $scope.search.age && $scope.search.age.length > 0)
      {
          return item.age && item.age.toString().indexOf($scope.search.age) > -1;
      }
      return true;
  }
Craig Squire
  • 2,141
  • 14
  • 13
  • Thanks for your input! I will be trying this out shortly. However, is it possible to somehow `undefine` `$scope.search.age` so that it runs just like the initial load? – Jesse Mar 07 '14 at 15:24
  • 1
    You can 'delete $scope.search.age' but I'm not sure how you would make that happen at the right time? – Craig Squire Mar 07 '14 at 15:36
  • I just found out about `delete`! I posted an answer implementing that solution. – Jesse Mar 07 '14 at 15:47