What is right way to delete element from object Angular JS? - angularjs

I have ng-repeat with function ng-click inside:
<div ng-repeat="item in data.education_list">
Delete
</div>
I pass object item from ng-repeat to function deleteEducation for deleting element from data.education_list.
So looks function:
$scope.deleteEducation = function (item){
$scope.data.education_list.splice($scope.data.education_list.indexOf(item), 1);
}
So this way works incorrect sometimes. When I have some element in ng-repeat and after delete item my template HTML is updated and removes row with another item, not that I deleted.
What is right way to delete?
data.education_list is array of objects if do {{data.education_list}}:
[{"name":"Test1","time":"01 Hun 2004 - 12 Sun 2006","Idusereducation":"86","usereducationIdToUser":"702","type":"1"}]
Problem two:
If I have object of objects instead array with key:
{"1" : {obj}, 2 : "obj"}
And if I try to delete element from object by key:
delete OBJ[1];
I get the same problem.

The easiest way is to use $index, this is a unique identifier that angular adds to track arrays.
<div ng-repeat="item in data.education_list">
Delete
</div>
If you are filtering a list
you will need to search for the index. Then do the splice. It is a little heavier, but required if you are filtering the list.
JS
this.removeItem = function(item) {
var index = $scope.data.education_list.indexOf(item);
if (index != -1) {
$scope.data.education_list.splice(index, 1);
}
};
HTML
ng-click="myctrl.removeItem(item)"
Working Example click to delete and .indexOf vs $index comparison

<div ng-repeat="item in data.education_list track by $index">
Delete
</div>
Then
$scope.deleteEducation = function (position){
$scope.data.education_list.splice(position, 1);
}

Came across the similar problem. In my case, I had to resolve as below just in case if it helps someone.
If you are dealing with objects, please note indexOf works for array not for an Object inside that array. You can do something like below to identify the index and handle this case;
$scope.removeReport = function(report) {
var index = $scope.contact.reports.map(function(r) { return r.id;}).indexOf(report.id);
if (index >= 0) {
$scope.contact.reports.splice(index, 1);
}
}

To remove a child item from a 2 dimensional array in an object, you need to define the parent item and then the child item to splice
e.g.
myObject[this.parentIndex].children.splice(this.childIndex,1);
myObject is an Object containing an array that includes children
parentIndex is the Index of the parent items
childIndex is the Index of the child items under the parent.
You can figure out your own way of looping through the parent and child arrays and deciding which child items to remove.

Related

What is the criteria for md-select to check duplicate options in md-options

I have been using Angular Material for a while in my project. While using md-select, I am stuck to a problem wherein I am getting Duplicate md-option values error.
I am aware that md-options takes unique values and I am assigning an array to md-options. This is however, an array of objects. So I would like to know what is the criteria that is used to differentiate objects. The API do not say much about it.
My use case demands to change md-options of an md-select, based on selection from another md-select. So I am watching the selection of first md-select and firing a watch on its change and updating md-options of second md-select.
Below is the approach I am using to assign array to md-options:
$scope.$watch('search.selectedTrades', function(newTrades, oldTrades) {
if ((newTrades.length === 0)) {
$rootScope.search.selectedTrades = oldTrades;
return;
}
if ($rootScope.search.selectedTrades && $rootScope.search.selectedTrades.length > 0) {
if (!$rootScope.identity.isClusterManager) {
$rootScope.search.selectedTrades = newTrades;
SearchFilterData.setSelectedTrades(newTrades);
$rootScope.search.selectedClusters = [];
$scope.clusters = [];
$scope.subareas = [];
var clusterKeys = [];
$rootScope.search.selectedTrades.forEach(function(t) {
t.lstClusters.forEach(function(c) {
if (clusterKeys.indexOf(c.ClusterKey) == -1) {
clusterKeys.push(c.ClusterKey);
$scope.clusters.push(c);
}
})
})
}
} else {
$scope.clusters = [];
$scope.subareas = [];
$rootScope.search.selectedClusters = [];
$rootScope.search.selectedSubAreas = [];
SearchFilterData.setSelectedTrades($rootScope.search.selectedTrades);
}
});
In above code, clusterKey is a unique entity for each object. So I am using it to push unique values into array.
This however happens on few random scenarios, after I have selected and de-selected various options. Please advise what I am doing wrong and what is the criteria for marking two objects duplicate
You did not provide your markup, so I cannot be sure, but in my case the problem was caused by omitting the double curleys on the 'value' attribute in the md-option tag.
This is bad: Notice the missing curly braces
<md-option ng-repeat="item in vm.list" value="item.id">{{item.text}}</md-option>
This is not:
<md-option ng-repeat="item in vm.itemlist" value="{{item.id}}">{{item.text}}</md-option>
I believe the reason that this fails is that each item will be placed into the option list will be given a value of 'item.id' (literally). It will fail on the second iteration of the repeat.
Using the curly braces causes the value in 'item.id' to be used.
Hope this helps.
Try using ng-value instead of just value attribute.
<md-option ng-repeat="item in vm.list" ng-value="item.id">{{item.text}}</md-option>

How to set class to first item in ng-repeat that has been sorted with orderBy?

I have a list of items, which comes in unsorted, I use orderBy to sort by name alphanumerically.
<li class="ticker-li"
ng-repeat="ticker in tickers | orderBy:'ticker'"
ng-class="{'selected':ticker.selected}">
<div class="ticker"
ng-click="unselectAll(); ticker.selected = !ticker.selected;
selectTicker(ticker);
revealTickerOptions()">
{{ticker.ticker}}
</div>
Now in my controller this is how I'm currently setting the first items selected class:
var vs = $scope;
vs.tickers = data;
vs.tickers[0].selected = true;
^ This worked perfectly until I needed to add the orderBy so that items appear by alpha order:
I found this answer here, however it locks the first item to always have the class.
Modifying my code a bit, I was able to have other buttons gain that class on click, however the $first item still stayed with the class.
ng-class="{'selected':$first || ticker.selected}"
In my controller this is my unselectAll function, which doesn't work with 'selected':$first:
vs.unselectAll = function() {
for (var i = 0; i < vs.tickers.length; i++) {
vs.tickers[i].selected = false;
}
};
How should the code either in the markup or controller need to be updated to fix this issue?
Give this a shot, I'm not sure how it reads the $index on the sort by, but get rid of the $first thing and put this init statement in there.
<li class="ticker-li"
ng-repeat="ticker in tickers | orderBy:'ticker'"
ng-init="$index ? ticker.selected = false : ticker.selected = true"
ng-class="{'selected':ticker.selected}" ng-click="unselectFirst($index)">
I think this is a grey area between a hack or not, you aren't technically aliasing a property in the ng-init, but i think it is a fine line. The other solution would be sort the array in your controller, there is an example in the docs that sort on alphabetic order, https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort

how to prevent duplicate in array push in angularjs

my code is like this :
var arr = [];
arr.push(item1,item2);
so arr will contain like:
["name","thing1"]
But i got problem when pushing element with same exact value, how do i filter same element value but still accepting update/changes. JSFIDDLE
You can use arr.indexOf which returns -1 if it is not found, so you can add it then.
e.g.
if (arr.indexOf(item) == -1) {
arr.push(item);
}
However, this does not work in old browsers...
JQuery has a method ($.indexOf) that works in every browser, even very old ones.
Just javascript is enough.
If an array contains the item its index is >= 0
so you can do this, if index == -1 , item does not exist in the array, so you can push unique item
if(arr.indexOf(item) == -1) {
arr.push(item);
}
Edit:
You asked for changing the number, this is how
var index = arr.indexOf(item);
if(index > -1) { //checking if item exist in array
arr[index]++; // you can access that element by using arr[index],
// then change it as you want, I'm just incrementing in above example
}
As what most of the answers have pointed out, you can use the Array.prototype.indexOf() method to determine if a value exists or not. In order to check for this, check the array of strings against your ng-models value property, selects.value, within the ng-change() event callback.
DEMO
Javascript
$scope.goChange = function(name, value){
if(!~arr.indexOf(value)) {
arr.push(name, value);
console.log(arr);
}
};
HTML
<select ng-model="selects" ng-change="goChange(item.name, selects.value)" ng-options="i as i.value for i in options">
<option value=""></option>
</select>
You should use Angular Filters.
Some implementations can be found as answers to a similar question: How to make ng-repeat filter out duplicate results
To filter duplicates in an array (es6):
let arr = ['foo', 'foo', 'bar', 'baz'];
Array.from(new Set(arr));
console.log(arr);
// ['foo', 'bar', 'baz'];

Ng-Repeat array to rows and columns

Thanks for taking the time to read this, I was wondering how I might be able to use ng-repeat to create a grid like box of options. I would like to take an array repeat nth number of items and then move to the next row or column until all items are listed. e.g.
assuming I had an array like [opt1,opt2,opt3,opt4,opt5,opt6,opt7] I would like to display it like this:
opt1 opt2 opt3
opt4 opt5 opt6
opt7
This is more a styling/markup problem than an AngularJS one. If you really want to, you can do:
<span ng:repeat="(index, value) in array">
{{value}}<br ng:show="(index+1)%3==0" />
</span>
http://jsfiddle.net/JG3A5/
Sorry for my HAML and Bootstrap3:
.row
.col-lg-4
%div{'ng:repeat' => "item in array.slice(0, array.length / 3)"}
{{item}}
.col-lg-4
%div{'ng:repeat' => "item in array.slice(array.length / 3, array.length * 2/3)"}
{{item}}
.col-lg-4
%div{'ng:repeat' => "item in array.slice(array.length * 2/3, array.length)"}
{{item}}
There is another version, with possibility to use filters:
<div class="row">
<div class="col-md-4" ng-repeat="remainder in [0,1,2]">
<span ng-repeat="item in array" ng-if="$index % 3 == remainder">{{item}}</span>
</div>
</div>
If all of your items are in one single array, your best bet is to make a grid in CSS. This article should be helpful: http://css-tricks.com/dont-overthink-it-grids/
You can use $index from ng-repeat to apply the correct class for your column (in this case a 4 column grid):
<div class="col-{{ $index % 4 }}"></div>
If you have a 2 dimensional array (split into rows and columns) that opens up more possibilities like actually using an HTML table.
I find it easier to simply use ng-repeat combined with ng-if and offsetting any indexes using $index. Mind the jade below:
div(ng-repeat="product in products")
div.row(ng-if="$index % 2 === 0")
div.col(ng-init="p1 = products[$index]")
span p1.Title
div.col(ng-if="products.length > $index + 1", ng-init="p2 = products[$index + 1]")
span p2.Title
div.col(ng-if="products.length <= $index + 1")
Between Performance, Dynamics and Readability
It seems putting the logic in your JavaScript is the best method. I would just bite-the-bullet and look into:
function listToMatrix(list, n) {
var grid = [], i = 0, x = list.length, col, row = -1;
for (var i = 0; i < x; i++) {
col = i % n;
if (col === 0) {
grid[++row] = [];
}
grid[row][col] = list[i];
}
return grid;
}
var matrix = listToMatrix(lists, 3);
console.log('#RedPill', matrix);
# Params: (list, n)
Where list is any array and n is an arbitrary number of columns desired per row
# Return: A matroid
# Note: This function is designed to orient a matroid based upon an arbitrary number of columns with variance in its number of rows. In other words, x = desired-columns, y = n.
You can then create an angular filter to handle this:
Filter:
angular.module('lists', []).filter('matrical', function() {
return function(list, columns) {
return listToMatrix(list, columns);
};
});
Controller:
function listOfListsController($scope) {
$scope.lists = $http.get('/lists');
}
View:
<div class="row" ng-repeat="row in (lists | matrical:3)">
<div class="col col-33" ng-repeat="list in row">{{list.name}}</div>
</div>
With this, you can see you get n number of rows -- each containing "3" columns. When you change the number of desired columns, you'll notice the number of rows changes accordingly (assuming the list-length is always the same ;)).
Here's a fiddle.
Note, that you get the ol' Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!. This is because Angular is recalling the matrical function upon every iteration. Allegedly, you can use the as results alias to prevent Angular from reevaluating the collection, but I had no luck. For this, it may be better to filter the grid inside of your controller and use that value for your repeater: $filter('matrical')(items) -- but please post back if you come across an elegant way of filtering it in the ng-repeat.
I would stress, again, you're probably heading down a dark alley by trying to write the logic in your view -- but I encourage you to try it in your view if you haven't already.
Edit
The use of this algorithm should be combined with a Matrical Data-Structure to provide methods of push, pop, splice, and additional methods -- in tandem with appropriate logic to complement Bi-Directional Data-Binding if desired. In other words, data-binding will not work out of the box (of course) as when a new item is added to your list, a reevaluation of the entire list must take place to keep the matrix's structural integrity.
Suggestion: Use the $filter('matrical')($scope.list) syntax in combination with $scope.$watch and recompile/calculate item-positions for the matrix.
Cheers!

AngularJS custom filter function

Inside my controller, I would like to filter an array of objects. Each of these objects is a map which can contain strings as well as lists
I tried using $filter('filter')(array, function) format but I do not know how to access the individual elements of the array inside my function. Here is a snippet to show what I want.
$filter('filter')(array, function() {
return criteriaMatch(item, criteria);
});
And then in the criteriaMatch(), I will check if each of the individual property matches
var criteriaMatch = function(item, criteria) {
// go thro each individual property in the item and criteria
// and check if they are equal
}
I have to do all these in the controller and compile a list of lists and set them in the scope. So I do need to access the $filter('filter') this way only. All the examples I found in the net so far have static criteria searches inside the function, they don't pass an criteria object and test against each item in the array.
You can use it like this:
http://plnkr.co/edit/vtNjEgmpItqxX5fdwtPi?p=preview
Like you found, filter accepts predicate function which accepts item
by item from the array.
So, you just have to create an predicate function based on the given criteria.
In this example, criteriaMatch is a function which returns a predicate
function which matches the given criteria.
template:
<div ng-repeat="item in items | filter:criteriaMatch(criteria)">
{{ item }}
</div>
scope:
$scope.criteriaMatch = function( criteria ) {
return function( item ) {
return item.name === criteria.name;
};
};
Here's an example of how you'd use filter within your AngularJS JavaScript (rather than in an HTML element).
In this example, we have an array of Country records, each containing a name and a 3-character ISO code.
We want to write a function which will search through this list for a record which matches a specific 3-character code.
Here's how we'd do it without using filter:
$scope.FindCountryByCode = function (CountryCode) {
// Search through an array of Country records for one containing a particular 3-character country-code.
// Returns either a record, or NULL, if the country couldn't be found.
for (var i = 0; i < $scope.CountryList.length; i++) {
if ($scope.CountryList[i].IsoAlpha3 == CountryCode) {
return $scope.CountryList[i];
};
};
return null;
};
Yup, nothing wrong with that.
But here's how the same function would look, using filter:
$scope.FindCountryByCode = function (CountryCode) {
// Search through an array of Country records for one containing a particular 3-character country-code.
// Returns either a record, or NULL, if the country couldn't be found.
var matches = $scope.CountryList.filter(function (el) { return el.IsoAlpha3 == CountryCode; })
// If 'filter' didn't find any matching records, its result will be an array of 0 records.
if (matches.length == 0)
return null;
// Otherwise, it should've found just one matching record
return matches[0];
};
Much neater.
Remember that filter returns an array as a result (a list of matching records), so in this example, we'll either want to return 1 record, or NULL.
Hope this helps.
Additionally, if you want to use the filter in your controller the same way you do it here:
<div ng-repeat="item in items | filter:criteriaMatch(criteria)">
{{ item }}
</div>
You could do something like:
var filteredItems = $scope.$eval('items | filter:filter:criteriaMatch(criteria)');

Resources