266

I am trying to write a function that enables me to remove an item when the button is clicked but I think I am getting confused with the function - do I use $digest?

HTML & app.js:

<ul ng-repeat="bday in bdays">
  <li>
    <span ng-hide="editing" ng-click="editing = true">{{bday.name}} | {{bday.date}}</span>
    <form ng-show="editing" ng-submit="editing = false">
      <label>Name:</label>
      <input type="text" ng-model="bday.name" placeholder="Name" ng-required/>
      <label>Date:</label>
      <input type="date" ng-model="bday.date" placeholder="Date" ng-required/>
      <br/>
      <button class="btn" type="submit">Save</button>
      <a class="btn" ng-click="remove()">Delete</a>
    </form>
  </li>
</ul>

$scope.remove = function(){
  $scope.newBirthday = $scope.$digest();
};
Mark Rajcok
  • 362,217
  • 114
  • 495
  • 492
Jess McKenzie
  • 8,345
  • 27
  • 100
  • 170
  • 2
    You don't want $digest, since that is used for entering Angular's digest loop (and you're already in a digest loop because of the ng-click). Are you trying to remove an item from an array? – Mark Rajcok Mar 16 '13 at 20:02
  • @MarkRajcok :) yes thats what im trying to do – Jess McKenzie Mar 16 '13 at 20:09
  • `remove()` in `ng-click` the way you have it has no context. Need more detail in markup to show what's being removed and if it is within `ng-repeat`, or where item being removed comes from, or what behavior you want from `remove()` – charlietfl Mar 16 '13 at 20:12
  • @charlietfl it is within ng-repeat I have updated the question – Jess McKenzie Mar 16 '13 at 20:20

12 Answers12

564

To remove item you need to remove it from array and can pass bday item to your remove function in markup. Then in controller look up the index of item and remove from array

<a class="btn" ng-click="remove(item)">Delete</a>

Then in controller:

$scope.remove = function(item) { 
  var index = $scope.bdays.indexOf(item);
  $scope.bdays.splice(index, 1);     
}

Angular will automatically detect the change to the bdays array and do the update of ng-repeat

DEMO: http://plnkr.co/edit/ZdShIA?p=preview

EDIT: If doing live updates with server would use a service you create using $resource to manage the array updates at same time it updates server

Andrew Swan
  • 13,427
  • 22
  • 69
  • 98
charlietfl
  • 170,828
  • 13
  • 121
  • 150
  • 62
    Using `$index` directly can generate bugs if your list is filtered on template. It's a template thing; it is safer to use `ng-click='remove(bday)'` then `arr.splice(arr.indexOf(bday),1);` – Umur Kontacı Mar 16 '13 at 22:03
  • @fastreload nice catch...am just getting nuances of angular figured out, see what you mean now. When filtered you end up with index of filtered array, not original data array – charlietfl Mar 16 '13 at 22:10
  • @fastreload Can I have an example – Jess McKenzie Mar 18 '13 at 18:34
  • @charlietfl it'd better if he changes his answer regarding that – Umur Kontacı Mar 18 '13 at 18:54
  • 6
    You don't need to pass $index because you can use 'this' inside the method. $scope.remove = function(){ $scope.bdays.splice(this.$index, 1); } – matchdav Aug 10 '13 at 20:20
  • 1
    @matthewdavidson `this is undefined`. Plunker/jsfiddle perhaps? – Tjorriemorrie Sep 19 '13 at 10:59
  • 11
    `.indexOf(item)` will return -1 if not found, this could result in removing the item at the end of the array if you don't check for it. – Ben Wilde Jul 28 '14 at 22:51
  • @levi can use polyfill for – charlietfl Feb 24 '15 at 17:49
  • What is the meaning of "1" in (index, 1) – ShibinRagh Sep 15 '15 at 11:31
  • 1
    @ShibinRagh read docs for [Array.prototype.splice()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice) – charlietfl Sep 15 '15 at 11:35
  • what happens if i have the html in `messages.html` across all my projects? I don't want to update all of my controllers with just this one snippet :( – Robert Johnstone Oct 12 '15 at 14:08
  • Hey, thanks for this solution! I'm working with angular2 and ionic2 and this works on a toast. Now i'm trying to do the "Undo" action. I did "this.bdays.push(index);" and the item is putted back but without the info inside. What have I missed? Can you help me? Thanks :) – sandrina-p May 26 '16 at 09:51
  • @SandrinaPereira start a new question and provide full details – charlietfl May 26 '16 at 20:31
  • Yh, i'm sorry about that. meanwhile I figured out a solution. Thank you and sorry again :) – sandrina-p May 27 '16 at 09:22
54

This is a correct answer:

<a class="btn" ng-click="remove($index)">Delete</a>
$scope.remove=function($index){ 
  $scope.bdays.splice($index,1);     
}

In @charlietfl's answer. I think it's wrong since you pass $index as paramter but you use the wish instead in controller. Correct me if I'm wrong :)

Dzung Nguyen
  • 9,152
  • 14
  • 65
  • 104
29

In case you're inside an ng-repeat

you could use a one liner option

    <div ng-repeat="key in keywords"> 
        <button ng-click="keywords.splice($index, 1)">

            {{key.name}}
        </button>
    </div>

$index is used by angular to show current index of the array inside ng-repeat

Community
  • 1
  • 1
azerafati
  • 18,215
  • 7
  • 67
  • 72
24

Using $index works perfectly well in basic cases, and @charlietfl's answer is great. But sometimes, $index isn't enough.

Imagine you have a single array, which you're presenting in two different ng-repeat's. One of those ng-repeat's is filtered for objects that have a truthy property, and the other is filtered for a falsy property. Two different filtered arrays are being presented, which derive from a single original array. (Or, if it helps to visualize: perhaps you have a single array of people, and you want one ng-repeat for the women in that array, and another for the men in that same array.) Your goal: delete reliably from the original array, using information from the members of the filtered arrays.

In each of those filtered arrays, $index won't be the index of the item within the original array. It'll be the index in the filtered sub-array. So, you won't be able to tell the person's index in the original people array, you'll only know the $index from the women or men sub-array. Try to delete using that, and you'll have items disappearing from everywhere except where you wanted. What to do?

If you're lucky enough be using a data model includes a unique identifier for each object, then use that instead of $index, to find the object and splice it out of the main array. (Use my example below, but with that unique identifier.) But if you're not so lucky?

Angular actually augments each item in an ng-repeated array (in the main, original array) with a unique property called $$hashKey. You can search the original array for a match on the $$hashKey of the item you want to delete, and get rid of it that way.

Note that $$hashKey is an implementation detail, not included in the published API for ng-repeat. They could remove support for that property at any time. But probably not. :-)

$scope.deleteFilteredItem = function(hashKey, sourceArray){
  angular.forEach(sourceArray, function(obj, index){
    // sourceArray is a reference to the original array passed to ng-repeat, 
    // rather than the filtered version. 
    // 1. compare the target object's hashKey to the current member of the iterable:
    if (obj.$$hashKey === hashKey) {
      // remove the matching item from the array
      sourceArray.splice(index, 1);
      // and exit the loop right away
      return;
    };
  });
}

Invoke with:

ng-click="deleteFilteredItem(item.$$hashKey, refToSourceArray)"

EDIT: Using a function like this, which keys on the $$hashKey instead of a model-specific property name, also has the significant added advantage of making this function reusable across different models and contexts. Provide it with your array reference, and your item reference, and it should just work.

XML
  • 19,206
  • 9
  • 64
  • 65
10

I usually write in such style :

<a class="btn" ng-click="remove($index)">Delete</a>


$scope.remove = function(index){
  $scope.[yourArray].splice(index, 1)
};

Hope this will help You have to use a dot(.) between $scope and [yourArray]

Community
  • 1
  • 1
  • What is the meaning of "1" in (index, 1) – ShibinRagh Sep 15 '15 at 11:30
  • @ShibinRagh It's the `deleteCount`. _An integer indicating the number of old array elements to remove. If deleteCount is 0, no elements are removed. In this case, you should specify at least one new element. If deleteCount is greater than the number of elements left in the array starting at start, then all of the elements through the end of the array will be deleted._ [Array.prototype.splice() Documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice) – ᴍᴀᴛᴛ ʙᴀᴋᴇʀ Oct 13 '15 at 10:34
9

Building on the accepted answer, this will work with ngRepeat, filterand handle expections better:

Controller:

vm.remove = function(item, array) {
  var index = array.indexOf(item);
  if(index>=0)
    array.splice(index, 1);
}

View:

ng-click="vm.remove(item,$scope.bdays)"
Joan-Diego Rodriguez
  • 2,439
  • 1
  • 27
  • 29
  • You didn't assign, "remove" to $scope.vm in your controller, so this code wouldn't work. Now if you did this... $scope.vm = {remove: function(){...}}, then it would. – Justin Russo Mar 03 '16 at 14:38
4

implementation Without a Controller.

<!DOCTYPE html>
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
<body>

<script>
  var app = angular.module("myShoppingList", []); 
</script>

<div ng-app="myShoppingList"  ng-init="products = ['Milk','Bread','Cheese']">
  <ul>
    <li ng-repeat="x in products track by $index">{{x}}
      <span ng-click="products.splice($index,1)">×</span>
    </li>
  </ul>
  <input ng-model="addItem">
  <button ng-click="products.push(addItem)">Add</button>
</div>

<p>Click the little x to remove an item from the shopping list.</p>

</body>
</html>

The splice() method adds/removes items to/from an array.

array.splice(index, howmanyitem(s), item_1, ....., item_n)

index: Required. An integer that specifies at what position to add/remove items, Use negative values to specify the position from the end of the array.

howmanyitem(s): Optional. The number of items to be removed. If set to 0, no items will be removed.

item_1, ..., item_n: Optional. The new item(s) to be added to the array

Deepu Reghunath
  • 8,132
  • 2
  • 38
  • 47
3

I disagree that you should be calling a method on your controller. You should be using a service for any actual functionality, and you should be defining directives for any functionality for scalability and modularity, as well as assigning a click event which contains a call to the service which you inject into your directive.

So, for instance, on your HTML...

<a class="btn" ng-remove-birthday="$index">Delete</a>

Then, create a directive...

angular.module('myApp').directive('ngRemoveBirthday', ['myService', function(myService){
    return function(scope, element, attrs){
        angular.element(element.bind('click', function(){
            myService.removeBirthday(scope.$eval(attrs.ngRemoveBirthday), scope);  
        };       
    };
}])

Then in your service...

angular.module('myApp').factory('myService', [function(){
    return {
        removeBirthday: function(birthdayIndex, scope){
            scope.bdays.splice(birthdayIndex);
            scope.$apply();
        }
    };
}]);

When you write your code properly like this, you will make it very easy to write future changes without having to restructure your code. It's organized properly, and you're handling custom click events correctly by binding using custom directives.

For instance, if your client says, "hey, now let's make it call the server and make bread, and then popup a modal." You will be able to easily just go to the service itself without having to add or change any of the HTML, and/or controller method code. If you had just the one line on the controller, you'd eventually need to use a service, for extending the functionality to the heavier lifting the client is asking for.

Also, if you need another 'Delete' button elsewhere, you now have a directive attribute ('ng-remove-birthday') you can easily assign to any element on the page. This now makes it modular and reusable. This will come in handy when dealing with the HEAVY web components paradigm of Angular 2.0. There IS no controller in 2.0. :)

Happy Developing!!!

Justin Russo
  • 2,214
  • 1
  • 22
  • 26
1

Here is another answer. I hope it will help.

<a class="btn" ng-click="delete(item)">Delete</a>

$scope.delete(item){
 var index = this.list.indexOf(item);
                this.list.splice(index, 1);   
}

array.splice(start)
array.splice(start, deleteCount)
array.splice(start, deleteCount, item1, item2, ...)

Full source is here
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice

0

if you have ID or any specific field in your item, you can use filter(). its act like Where().

<a class="btn" ng-click="remove(item)">Delete</a>

in controller:

$scope.remove = function(item) { 
  $scope.bdays = $scope.bdays.filter(function (element) {
                    return element.ID!=item.ID
                });
}
0
Pass the id that you want to remove from the array to the given function 

from the controller( Function can be in the same controller but prefer to keep it in a service)

    function removeInfo(id) {
    let item = bdays.filter(function(item) {
      return bdays.id=== id;
    })[0];
    let index = bdays.indexOf(item);
    data.device.splice(indexOfTabDetails, 1);
  }
Utkarsh Joshi
  • 141
  • 1
  • 3
0

An inline simple way is just add bdays.splice($index, 1) in your delete button.

  <ul ng-repeat="bday in bdays">
  <li>
    <span ng-hide="editing" ng-click="editing = true">{{bday.name}} | {{bday.date}}</span>
    <form ng-show="editing" ng-submit="editing = false">
      <label>Name:</label>
      <input type="text" ng-model="bday.name" placeholder="Name" ng-required/>
      <label>Date:</label>
      <input type="date" ng-model="bday.date" placeholder="Date" ng-required/>
      <br/>
      <button class="btn" type="submit">Save</button>
      <a class="btn" ng-click="bdays.splice($index, 1)">Delete</a>
    </form>
  </li>
</ul>
Malik Zahid
  • 593
  • 5
  • 13