11

I want to use Javascript's array.filter to remove items from an array, because the syntax is elegant and readable. However, it seems that filter doesn't modify the original array, it just returns a new array, filtered as you requested. My question is, why doesn't the following work as I expect?

$scope.clearList = function () {
  this.list = this.list.filter(function (item) {
    return item.checked == true;
  });

  //...
}

I would expect that after returning the newly filtered array, this.list would now hold just the filtered set. It does not work like this however. this.list ends up containing exactly the same items. Altering the code to save out the filtered array in an intermediate variable shows that it is indeed filtering correctly.

I've done a workaround for now, looping through the filtered version and splicing items out of the original list that should be filtered, but this is inelegant. Am I just thinking about it the wrong way?


Sidenote: I'm using Angular.js. I'm unsure it matters at all, but the list comes from the following:

  <div class="list" ng-repeat="list in lists">
    <!-- ... -->
    <ul>
      <li ng-repeat="item in list">
        <div>
          <label>
            <input type="checkbox" ng-model="item.checked"/>
            {{item.name}}
          </label>
          <!-- ... -->
        </div>
      </li>
    </ul>
    <button class="btn clear-selected" ng-click="clearList()">
      Remove Selected
    </button>
  </div>

Edit to add debugging info: I've introduced a temp variable just to see whats going on in the debugger.

var temp = this.list.filter(function (item) {
  return item.checked == true;
});

this.list = temp;

Before execution, this.List has 5 items, temp is undefined. After the first line is executed, this.List has 5 items, and temp has 2 items. After the last line is executed, this.List has 2 items, temp has 2 items.

However, it seems after this that the UI which is bound to this.list does not update. So something unrelated to filter does seem to be going on.

4
  • 1
    Well, I've done a workaround involving foreach. It just seems like there must be a cleaner way. Further, aren't you not supposed to modify the collection you're foreaching over, e.g. removing items from it? I know this is true in C# .NET. Commented Dec 14, 2012 at 20:44
  • Yeah, I read your question wrong earlier. Deleted my previous comment. Commented Dec 14, 2012 at 20:49
  • Do you have an example page somewhere? This may help. The only thing that confuses me is that you filter this.list and I don't see any $scope there. I may get something wrong but unless your list is a property of the $scope the UI can't update. Oh and another thing: You have a directive within an directive (list belongs to the scope of the first ng-repeat as far as I can remember) That can be very tricky, I would guess that the error lies somewhere there. But maybe I am wrong :) Commented Dec 14, 2012 at 22:07
  • Here's what's going on: jsfiddle.net/mvmPJ/1 It's obviously missing some features and the style is jacked, but that's pretty much what I've got going right now. There's also my commented out "workaround" as well. Commented Dec 17, 2012 at 14:52

3 Answers 3

7

In angular you modify data using the special $scope variable, and while inside your controller this points to the $scope as the executing context, $scope is preferred.

When the UI doesn't update, it's usually because changes in "models" (or properties of a given scope) are done outside angular. In this case a call to $apply is needed. This notifies angular that something has changed and to update the views.

However, this doesn't seem to be your issue. I have a working list with minimal changes here http://plnkr.co/edit/Cnj0fEHSmi2L8BjNRAf5?p=preview

Here's the contents of the controller and when you call clearList() from the UI, only checked items are left in the list.

$scope.list = [
  {name: 'one', checked: true},
  {name: 'two', checked: false},
  {name: 'three', checked: true},
  {name: 'four', checked: false}
];

$scope.clearList = function () {
  $scope.list = $scope.list.filter(function(item) {
    return item.checked === true;
  });
};  

Now, I'd recommend passing a list to clearList clearList(list) or even better use Angular filters.

Sign up to request clarification or add additional context in comments.

1 Comment

Passing in list to clearList worked well, and using angular filters, I didn't even need to pass in list. this.list worked just fine in this case. Thanks!
5
window.list = [1,2,3,4,5,6];
var clearList = function () {
    this.list = this.list.filter(function (item) { return item % 2 === 0; });
};
clearList();
console.log(window.list);

Logs [2, 4, 6] as expected, so I think whatever your bug is is unrelated to filter.

Are you sure that the array you're modifying with this.list is the same array that you later come to check?

1 Comment

It appears it may not be. I've added additional info to the question that shows what I see in the debugger.
1

I hope this gives an inspiration or a different perspective on how array.filter() can modify the original array.

let words = ["four", "digit", "excellent", "destruction", "grass", "present"];
    
    const moddedWords = words.filter((word, index, arr) => {
      arr[index + 1] += " extra";
      return word.length < 6;
    });
    
    console.log(moddedWords, words);

Notice how the original array; "words" has changed and been modified.

words = ['four', 'digit extra', 'excellent extra', 'destruction extra', 'grass extra', 'present extra', 'undefined extra']

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.