Replace object with existing value in array in AngularJS - javascript

I have an ng-repeat with a select in every item.
The user can select a value (trigging a function that pushes the an object into an array), but they can also change their mind, in which case the code just pushes a second object with the new value, duplicating the first one.
How could I manage to actually delete existing values, leaving only the last one on every ng-change?
Here's my HTML:
<select ng-change="insertproduct(pa.nom, basket)" ng-model="basket">
<option ng-repeat="select in numberofproducts">{{select}}</option>
</select>
And my javascript:
$scope.numberofproducts = [1,2,3,4,5,6,7,8,9,10]
$scope.singleorder = [];
$scope.insertproduct = function(nom, basket){
$scope.numero = {
'producte': nom,
'numero': basket
};
$scope.singleorder.push($scope.numero);
console.log($scope.singleorder);
}
The idea is to create a condition in which if the array contains an object with the parameter ´producte´ equal to the new one, delete the existing and push the new one.
Any tips?

First, use the findIndex method to check if an object with the same property is already in the singleorder array.
function duplicateOrder(order) {
return order.producte === nom;
}
var index = $scope.singleorder.findIndex(duplicateOrder);
Note: browser support for findIndex is limited; it is not supported in Internet Explorer.
Then remove the item with splice:
if(index > -1){
$scope.singleorder.splice(index, 1);
}
You can then push the new one in.
You should also clean up your coding style: don't mix french and english, and use either camelCase or snake_case for your functions to improve readability.

Observation :
Use AngularJS ngOptions attribute instead of ng-repeat.
You can check the index of the element in an array if that was already there you can easily remove previous one.
DEMO
var myApp = angular.module('myApp',[]);
myApp.controller('MyCtrl',function($scope) {
$scope.numberofproducts = [1,2,3,4,5,6,7,8,9,10]
$scope.newArray = [];
$scope.insertproduct = function(basket) {
var prevIndex = $scope.newArray.indexOf(basket);
if(prevIndex > -1) {
$scope.newArray.splice(prevIndex, 1);
} else {
$scope.newArray.push(basket);
}
console.log($scope.newArray);
}
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller="MyCtrl">
<select ng-change="insertproduct(basket)" ng-model="basket" ng-options="select for select in numberofproducts">
</select>
</div>

Related

How to filter array of items with multiple dropdowns on single angularjs custom filter function?

I am facing a problem with the filter function in Angularjs, here I used a common function ng-change for multiple a dropdown. But when I select All(default options) I am getting an empty array of filtered items.
Here is my code
Javascript:
function customFilters() {
vm.filteredItems = $filter('filter')(vm.claimsResData, {
status: vm.status,
member: vm.member,
treatment: vm.treatment
});
}
Html:
<select ng-model="vm.member" class="form-control"
ng-options="names for names in vm.memberNames"
ng-change="vm.customFilters()">
<option value="">All Members</option>
</select>
Any help would be appreciated.
Thanks.
Update your function as below, because when you want to exclude a key from filter you've to pass undefined to its value, but in your code you are passing blank string i.e., ""
function customFilters() {
vm.filteredItems = $filter('filter')(vm.claimsResData, {
status: vm.status,
member: vm.member || undefined,
treatment: vm.treatment
});
}
Only add the selection to the pattern object if the selection is truthy:
function customFilters() {
var patternObj = {}
vm.status && patternObj.status = vm.status;
vm.member && patternObj.member = vm.member;
vm.treatment && patternObj.treatment = vm.treatment;
vm.filteredItems = $filter('filter')(vm.claimsResData, patternObj);
}
A pattern object is used to filter specific properties on objects contained by the array. When that property exists on the pattern object, the objects in the array must have that property.
For more information, see
AngularJS filter Filter API Reference

Using a double groupBy in lodash

So I am trying to categorize an array of objects by a certain attribute. Using groupBy works great the first time. Now I need to loop through those groupings and group them again based on a separate attribute. I am having trouble with this can someone help me out?
TS
this.accountService.getAccountListWithBalance().subscribe(accounts => {
this.accountList = _.groupBy(accounts, 'category');
for (var property in this.accountList) {
if (this.accountList.hasOwnProperty(property)) {
this.accountList.property = _.groupBy(this.accountList.property, 'subcategory');
}
}
generateArray(obj){
return Object.keys(obj).map((key)=>{ return {key:key, value:obj[key]}});
}
HTML:
<ul *ngFor="let item of generateArray(accountList)">
<strong>{{ item.key }}</strong>
<li *ngFor="let i of item.value">{{i.name}}</li>
</ul>
The HTML isnt set for the next level of interation but I know it isnt working if I just console log the resulting object. Like I said its being sorted the first time just not the second time.
I was able to get it to work by just changing my syntax. Using [] instead of . so the working code is as follows.
this.accountService.getAccountListWithBalance().subscribe(accounts => {
this.accountList = _.groupBy(accounts, 'category');
for (var property in this.accountList) {
if (this.accountList.hasOwnProperty(property)) {
this.accountList[property] = _.groupBy(this.accountList[property], 'subcategory');
}
}
I believe that when you are looping through accounts grouped by category, you should try group each category grouped item based on subcategory like this;
this.accountList = _.groupBy(accounts, 'category');
_.foreach(this.accountList, function(categoryAccount) {
_.groupBy(categoryAccount, 'subcategory');
});

remove and re-add same element to array on click event

I'm currently making a small app to practice JavaScript and jQuery.
I have an array of fruits.
var fruits = [apples, bananas, grapes, watermelons, tomatoes];
And on a click event I want to remove tomatoes but if I change my mind clicking the same button, it will add tomatoes back to the array fruits. I've been using splice to remove but I don't know what to use to add the splice element back into the array.
Edit for clarification:
The element is not necessary going to be tomatoes, but it could be any random element in the fruits array. I'm using
fruits.splice(i,1);
To insert a value back into an array (at the same position) after you spliced it, can in general be done like this:
// delete:
var deleted = fruits.splice(i, 1);
// restore:
fruits.splice(i, 0, deleted);
Note that deleted is an array with one element here.
It can also done by taking a kind of backup of the original array:
// backup
var backup = fruits.slice(); // copy
// delete:
fruits.splice(i, 1);
// restore:
fruits = backup;
Undo Stack
To support multiple undo actions, you could use an undo stack, which would just keep track of all the versions of your array. When the user performs an undo-action, the previous version is popped from that stack. This way you can undo more than one removal:
var fruits = ['Apples', 'Bananas', 'Grapes', 'Watermelons', 'Tomatoes'];
var undoStack = [];
function showFruits() {
$('#fruits').html('').append(
// translate the array to a list of LI elements with delete buttons
fruits.map(function(fruit) {
return $('<li>').text(fruit).append(
$('<button>').addClass('delete').text('Delete'));
})
);
}
$(document).on('click', 'button.delete', function () {
undoStack.push(fruits.slice()); // save a copy of the current array on the stack
fruits.splice($(this).parent().index(), 1); // remove from array at index
showFruits(); // update display
});
$('#undo').click(function () {
if (!undoStack.length) return; // cannot undo
fruits = undoStack.pop(); // get previous state
showFruits(); // update display
});
showFruits(); // show initial list
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul id="fruits"></ul>
<button id="undo">Undo</button>
More memory efficient alternative
If you are troubled by the memory usage of storing the complete array each time you delete an element, you could use the following alternative functions, which will only store the index and deleted value at every delete action:
$(document).on('click', 'button.delete', function () {
var i = $(this).parent().index(); // get index where to delete
var deleted = fruits.splice(i, 1); // remove from array at that index
undoStack.push([i, deleted]); // save the index and value on the stack
showFruits(); // update display
});
$('#undo').click(function () {
if (!undoStack.length) return; // cannot undo
var restore = undoStack.pop(); // get information for re-inserting
fruits.splice(restore[0], 0, restore[1]); // insert the value
showFruits(); // update display
});
If you would use the undo principle also for other modifications, like undoing an insert, or a modification of the label, then the first solution would not need much modification, while the more memory-efficient one would need a bit more.
For a more generic and elaborated solution on undo/redo operations on any object (not only arrays), see How to version control an object?
If the array position does not matter:
fruits.push(tomatoes);
If you want to insert it at a specific position (index) in the array:
fruits.splice(index, 0, tomatoes);
will insert tomatoes into fruits at the specified index (deleting 0 items first, so it's just an insert).
Array.prototype.splice()
The splice() method changes the content of an array by removing existing elements and/or adding new elements.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice
Temp-storing deleted elements and re-adding them
var deletedFruits = fruits.splice(i,1); will contain an array of the removed element(s) because that is the return value of splice(). So
fruits = fruits.concat(deletedFruits);
will re-add the deleted fruits.
Re-adding deleted elements at their original position
Store the position of the deleted element:
var deletedFruit = { fruit: fruits.splice(i,1)[0], index: i }
If need be you can restore deleted fruits at their original array position using the aforementioned
fruits.splice(deletedFruit.index, 0, deletedFruit.fruit);
You can add and remove fruits with these two functions:
function addFruit(fruit) {
fruits.push(fruit);
}
function removeFruit(fruit) {
// indexOf method returns index of fruit in the list, or -1 if fruit is not found.
var index = fruits.indexOf(fruit);
if (index > -1) {
fruits.splice(index, 1);
}
}
This assumes you have already defined an array named fruits. Then you can do something like
<script>
function updateOutput() {
document.getElementById('output').innerHTML = fruits.join(', ');
}
function addSelectedFruit() {
var selectedFruit = document.getElementById('fruit-select').value;
addFruit(selectedFruit);
updateOutput();
}
function removeSelectedFruit() {
var selectedFruit = document.getElementById('fruit-select').value;
removeFruit(selectedFruit);
updateOutput();
}
</script>
<input type="text" id="fruit-select"/>
<button onclick="addSelectedFruit();">Add</button>
<button onclick="removeSelectedFruit();">Remove</button>
List of fruits:
<p id="output"></p>
Example:
<script>
var fruits = ['Apples', 'Pears', 'Pineapples'];
function addFruit(fruit) {
fruits.push(fruit);
}
function removeFruit(fruit) {
var i = fruits.indexOf(fruit);
if (i > -1) {fruits.splice(i, 1);}else{alert(fruit + ' cannot be removed, as it is not present in the array of fruits.');}
}
function selectedFruit() {
return document.getElementById('fruit-select').value;
}
function updateOutput() {
document.getElementById('output').innerHTML = fruits.join(', ');
}
</script>
Fruit:
<input type="text" id="fruit-select" value="Orange"/>
<button onclick="addFruit(selectedFruit());updateOutput();">Add</button>
<button onclick="removeFruit(selectedFruit());updateOutput();">Remove</button>
<p id="output">Apples, Pears, Pineapples</p>

ng-repeat track by $index and removing elements from the array

I am using track by $index because I want to allow repeated elements in my array, but at the same time this is causing a side effect when removing elements from this collection.
I have this set of players which is declared in the controller as $scope.players = [].
You can populate this array as follows:
<input type="text" ng-model="player">
<button ng-click="addPlayer()">
addPlayer() just pushes the player model to the players array:
$scope.addPlayer = function() {
if (!$scope.player)
return;
$scope.players.push($scope.player);
$scope.player = null;
};
And the collection is shown using ng-repeat. But also when an item is clicked on, it should be deleted.
<div ng-repeat="player in players track by $index" ng-click="deletePlayer($index)">
{{player}}
</div>
$scope.deletePlayer = function(index) {
if (index > -1)
$scope.players.splice(index, 1);
};
The issue is that since it's tracking by index, when an element is removed the collection of players will be short by 1 because the collection has changed.
What I mean by this is the following: say I have the array of players ["p1", "p2", "p3"]. If I remove one of these except the last, for example, p1, the ng-repeat is not showing [p2, p3] even though these are the contents of the array, but it shows just p3. This is what I mean when I say the collection is one element short.
I think the issue happens because it's unknown to ng-repeat in the track by $index mode that the length of the array has changed. Therefore, it's skipping one element when iterating through the changed array, because it's using the old indices to iterate it, I believe.
Is there a standard way of tackling this side effect?
You can make each item in players array to be an object that has name and id properties. Demo.
Object.assign($scope, {
players: [],
player: '',
addPlayer: function() {
if(!$scope.player) {
return
}
$scope.players = $scope.players.concat({
name: $scope.player,
id: Date.now() //fake id (timestamp)
})
$scope.player = ''
},
deletePlayer: function(id) {
$scope.players = $scope.players.filter(function(player){
return player.id !== id
})
}
})
<div ng-repeat="player in players track by player.id" ng-click="deletePlayer(player.id)">
{{player.name}}
</div>
The problem is with your deletePlayer function. Your argument name is 'i' but you are trying to use 'index' instead.
This:
$scope.deletePlayer = function(i) {
if (i > -1)
$scope.players.splice(index, 1);
};
should be:
$scope.deletePlayer = function(i) {
if (i > -1)
$scope.players.splice(i, 1);
};
In the element you could use: ng-click="remove(phones, $index)
And in the code:
$scope.remove = function(array, index){
array.splice(index, 1);
}
You shouldn't have any problems with this approach.

angularJs exclude already selected items from array

I have an array of objects in $scope.currentSChannels.scgsLink This array of objects have something like
$scope.currentSChannels.scgsLink = [{channelId:1, sCgsLinkId:1, groupNo:1, percentage: 50, expireHrs:4},{channelId:1, sCgsLinkId:2, groupNo:2, percentage:50, expireHrs:1}]
and I also have the following select list
<div class="col-md-4">
<select class="form-control" ng-model="newLink.groupNo"
name="groupNo" id="groupNo"
ng-options="t.value as t.text for t in metaData.spGroups"></select>
</div>
I need to filter that list to not show already selected items in the $scope.currentSChannels.scgsLink groupNo column. I looked at http://christian.fei.ninja/Angular-Filter-already-selected-items-from-ng-options/ and also at AngularJS ng-options to exclude specific object and both seem to be close but not enough as I need to filter against an array and a particular column in that array. How should I implement that filtering?
The template is getting a bit tricky. Assuming selectedLink is the variable that points to the selected groupNo
ng-options="t.value as t.text for t in metaData.spGroups | filter: {value: '!' + currentSChannels.scgsLink[selectedLink].groupNo}"
See this fiddle : the second select contains the same collection as the first one, excluded what is already selected.
Edit: Solution above is for excluding elements according to one value. So as to exclude the elements according to a collection of values, a custom filter would suit best:
Filter
app.filter('channelFilter', function () {
return function (metadata, exclusions) {
var filterFunction = function (metadata) {
// return the metadata object if exclusions array does NOT contain his groupNo
return !exclusions.some(function (exclusion) {
return exclusion.groupNo === metadata.value;
});
};
return metadatas.filter(filterFunction);
};
});
Usage
ng-options="metadata in metadatas | channelFilter: exclusions"
Template
ng-options="t.value as t.text for t in metaData.spGroups | channelFilter: currentSChannels.scgsLink"
Fiddle
That said, would be more efficient to group selected links by groupNo to avoid searches in the array, and filter in the controller.
I wanted to make it a bit more generic, so I've done the following
http://jsfiddle.net/96m4sfu8/
app.filter('excludeFrom', function () {
return function (inputArray, excludeArray, excludeColumnName, inputColumnName) {
if (inputColumnName==undefined)
inputColumnName = 'value';
var filterFunction = function (inputItem) {
return !excludeArray.some(function (excludeItem) {
return excludeItem[excludeColumnName] === inputItem[inputColumnName];
});
};
return inputArray.filter(filterFunction);
};
});

Categories

Resources