72

I am trying to remove elements from the array $scope.items so that items are removed in the view ng-repeat="item in items"

Just for demonstrative purposes here is some code:

for(i=0;i<$scope.items.length;i++){
    if($scope.items[i].name == 'ted'){
      $scope.items.shift();
    }
}

I want to remove the 1st element from the view if there is the name ted right? It works fine, but the view reloads all the elements. Because all the array keys have shifted. This is creating unnecessary lag in the mobile app I am creating..

Anyone have an solutions to this problem?

Dan Beaulieu
  • 19,406
  • 19
  • 101
  • 135
TheNickyYo
  • 2,389
  • 5
  • 20
  • 28
  • I've used splice successfully to modify an array that is used in ng-repeat with no weird side effects. – BoxerBucks Aug 18 '13 at 19:57
  • Looks like the items is an array of array, or you can't call items[i].shift(); – zs2020 Aug 18 '13 at 20:12
  • Hi, thanks for the replies. Sorry, there was a typos in the code of my question, i've just updated it. – TheNickyYo Aug 18 '13 at 20:21
  • Then why you remove the first element from the array rather than the element at the position i? – zs2020 Aug 18 '13 at 20:25
  • 3 rows will solve you the problem, just add $filter in controller – Maxim Shoustin Aug 18 '13 at 21:25
  • Possible duplicate of [How do I delete an item or object from an array using ng-click?](http://stackoverflow.com/questions/15453979/how-do-i-delete-an-item-or-object-from-an-array-using-ng-click) – azerafati Nov 23 '16 at 08:42

12 Answers12

138

There is no rocket science in deleting items from array. To delete items from any array you need to use splice: $scope.items.splice(index, 1);. Here is an example:

HTML

<!DOCTYPE html>
<html data-ng-app="demo">
  <head>
    <script data-require="angular.js@1.1.5" data-semver="1.1.5" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.js"></script>
    <link rel="stylesheet" href="style.css" />
    <script src="script.js"></script>
  </head>
  <body>
    <div data-ng-controller="DemoController">
      <ul>
        <li data-ng-repeat="item in items">
          {{item}}
          <button data-ng-click="removeItem($index)">Remove</button>
        </li>
      </ul>
      <input data-ng-model="newItem"><button data-ng-click="addItem(newItem)">Add</button>
    </div>
  </body>
</html>

JavaScript

"use strict";

var demo = angular.module("demo", []);

function DemoController($scope){
  $scope.items = [
    "potatoes",
    "tomatoes",
    "flour",
    "sugar",
    "salt"
  ];

  $scope.addItem = function(item){
    $scope.items.push(item);
    $scope.newItem = null;
  }

  $scope.removeItem = function(index){
    $scope.items.splice(index, 1);
  }
}
madhead
  • 31,729
  • 16
  • 153
  • 201
124

For anyone returning to this question. The correct "Angular Way" to remove items from an array is with $filter. Just inject $filter into your controller and do the following:

$scope.items = $filter('filter')($scope.items, {name: '!ted'})

You don't need to load any additional libraries or resort to Javascript primitives.

bpaul
  • 1,949
  • 1
  • 13
  • 23
  • Would this work if you have an element with the name "ted" and "teddy", and just want to delete the "ted" element? – Jervelund Oct 03 '14 at 20:41
  • 2
    @Jervelund yes you can by using true at the end: ``$filter('filter')($scope.items, {name: '!ted'}, true)`` , see https://docs.angularjs.org/api/ng/filter/filter – Betty St Oct 07 '14 at 12:09
  • 4
    I think this answer should probably be the accepted one because of its angular-ness. – MrBoJangles Jan 13 '15 at 23:24
  • Is it possible to use filter to remove an element containing a specific numeric id? `$filter('filter')($scope.items, {id: 42}, true);` works for finding the element, but I haven't found a way to negate it, `{id: "!42"}` doesn't work since it's a string instead. – Oscar Feb 16 '15 at 08:46
  • 29
    This works: `$filter('filter')($scope.items, function(value, index) {return value.id !== 42;});` – Oscar Feb 16 '15 at 10:44
  • despite this answer's "angular-ness" it's a lot less readable/usable than the splice method – A_funs Mar 20 '15 at 02:53
16

You can use plain javascript - Array.prototype.filter()

$scope.items = $scope.items.filter(function(item) {
    return item.name !== 'ted';
});
Bogdan D
  • 5,321
  • 2
  • 31
  • 32
11

Because when you do shift() on an array, it changes the length of the array. So the for loop will be messed up. You can loop through from end to front to avoid this problem.

Btw, I assume you try to remove the element at the position i rather than the first element of the array. ($scope.items.shift(); in your code will remove the first element of the array)

for(var i = $scope.items.length - 1; i >= 0; i--){
    if($scope.items[i].name == 'ted'){
        $scope.items.splice(i,1);
    }
}
zs2020
  • 53,766
  • 29
  • 154
  • 219
8

Here is filter with Underscore library might help you, we remove item with name "ted"

$scope.items = _.filter($scope.items, function(item) {
    return !(item.name == 'ted');
 });
Maxim Shoustin
  • 77,483
  • 27
  • 203
  • 225
  • 2
    This is the "angular" way! Of course it can be improved: ```$scope.items = $filter('filter').filter($scope.items, {name: '!ted'}) ``` – bpaul Feb 04 '14 at 00:29
  • 3
    Nice improvement from @bpaul but looks like there is an error, it should be: `$scope.items = $filter('filter')($scope.items, {name: '!ted'})` – Eugene Mar 03 '14 at 00:31
  • 1
    @Eugene not at all. I used Underscore library, added link. – Maxim Shoustin Mar 03 '14 at 05:50
  • Thanks @Eugene, that was a typo. I should test code before I post! Also, this should be an answer as it does not need underscore (or any other lib) and is idiomatic angular. I'll add it. – bpaul Mar 04 '14 at 00:31
7

I liked the solution provided by @madhead

However the problem I had is that it wouldn't work for a sorted list so instead of passing the index to the delete function I passed the item and then got the index via indexof

e.g.:

var index = $scope.items.indexOf(item);
$scope.items.splice(index, 1);

An updated version of madheads example is below: link to example

HTML

<!DOCTYPE html>
<html data-ng-app="demo">
  <head>
    <script data-require="angular.js@1.1.5" data-semver="1.1.5" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.1.5/angular.js"></script>
    <link rel="stylesheet" href="style.css" />
    <script src="script.js"></script>
  </head>
  <body>
    <div data-ng-controller="DemoController">
      <ul>
        <li data-ng-repeat="item in items|orderBy:'toString()'">
          {{item}}
          <button data-ng-click="removeItem(item)">Remove</button>
        </li>
      </ul>
      <input data-ng-model="newItem"><button data-ng-click="addItem(newItem)">Add</button>
    </div>
  </body>
</html>

JavaScript

"use strict";

var demo = angular.module("demo", []);

function DemoController($scope){
  $scope.items = [
    "potatoes",
    "tomatoes",
    "flour",
    "sugar",
    "salt"
  ];

  $scope.addItem = function(item){
    $scope.items.push(item);
    $scope.newItem = null;
  }

  $scope.removeItem = function(item){
    var index = $scope.items.indexOf(item);
    $scope.items.splice(index, 1);
  }
}
TrtlBoy
  • 669
  • 14
  • 17
6

Just a slight expansion on the 'angular' solution. I wanted to exclude an item based on it's numeric id, so the ! approach doesn't work. The more general solution which should work for { name: 'ted' } or { id: 42 } is:

mycollection = $filter('filter')(myCollection, { id: theId }, function (obj, test) { 
                                                             return obj !== test; });
Mark Farmiloe
  • 396
  • 3
  • 8
2

My solution to this (which hasn't caused any performance issues):

  1. Extend the array object with a method remove (i'm sure you will need it more than just one time):
Array.prototype.remove = function(from, to) {
  var rest = this.slice((to || from) + 1 || this.length);
  this.length = from < 0 ? this.length + from : from;
  return this.push.apply(this, rest);
};

I'm using it in all of my projects and credits go to John Resig John Resig's Site

  1. Using forEach and a basic check:
$scope.items.forEach(function(element, index, array){
          if(element.name === 'ted'){
              $scope.items.remove(index);
          }
        });

At the end the $digest will be fired in angularjs and my UI is updated immediately without any recognizable lag.

Fer To
  • 1,487
  • 13
  • 24
  • Is it really safe to remove an element while iterating over the list? – Volte Oct 08 '15 at 04:19
  • Generally, you should be careful about doing this. I would say using .forEach and not a for loop makes it safe as MDN https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach says: "forEach() executes the provided callback once for each element present in the array in ascending order. It is not invoked for index properties that have been deleted or are uninitialised (i.e. on sparse arrays)." So deleted items after visit / or before visit are just "ignored" which imho makes it safe for this case. – Fer To Oct 08 '15 at 10:28
  • This is NOT safe in the current form if you need to remove more than 1 item. After the first removal the `index` will still reflect the index from the original array but the index of the same item in the current `$scope.items` will be one value less for each of the preceeding items that were removed. So while it is safe to remove items while iterating using `forEach`, it is **NOT SAFE** to use the index provided in the callback to do so. – Chris Schaller Apr 12 '22 at 17:07
  • This is true. For removing an item I would recommend using a mix of either .reduce or .filter. In both cases you would need to assign the new array to $scope.items. That said, Angularjs is officially archived and end of life. So anyone who is reading this, this code most likely is outdated. – Fer To Apr 27 '22 at 11:19
1

If you have any function associated to list ,when you make the splice function, the association is deleted too. My solution:

$scope.remove = function() {
    var oldList = $scope.items;
    $scope.items = [];

    angular.forEach(oldList, function(x) {
        if (! x.done) $scope.items.push( { [ DATA OF EACH ITEM USING oldList(x) ] });
    });
};

The list param is named items. The param x.done indicate if the item will be deleted. Hope help you. Greetings.

Drako
  • 769
  • 1
  • 8
  • 23
1

Using the indexOf function was not cutting it on my collection of REST resources.

I had to create a function that retrieves the array index of a resource sitting in a collection of resources:

factory.getResourceIndex = function(resources, resource) {
  var index = -1;
  for (var i = 0; i < resources.length; i++) {
    if (resources[i].id == resource.id) {
      index = i;
    }
  }
  return index;
}

$scope.unassignedTeams.splice(CommonService.getResourceIndex($scope.unassignedTeams, data), 1);
Stephane
  • 11,836
  • 25
  • 112
  • 175
1

My solution was quite straight forward

app.controller('TaskController', function($scope) {
 $scope.items = tasks;

    $scope.addTask = function(task) {
        task.created = Date.now();
        $scope.items.push(task);
        console.log($scope.items);
    };

    $scope.removeItem = function(item) {
        // item is the index value which is obtained using $index in ng-repeat
        $scope.items.splice(item, 1);
    }
});
Bastin Robin
  • 907
  • 16
  • 30
0

My items have unique id's. I am deleting one by filtering the model with angulars $filter service:

var myModel = [{id:12345, ...},{},{},...,{}];
...
// working within the item
function doSthWithItem(item){
... 
  myModel = $filter('filter')(myModel, function(value, index) 
    {return value.id !== item.id;}
  );
}

As id you could also use the $$hashKey property of your model items: $$hashKey:"object:91"

Ruwen
  • 3,008
  • 1
  • 19
  • 16